温馨提示×

Ubuntu上Fortran网络编程怎么操作

小樊
31
2025-12-28 03:32:28
栏目: 智能运维

Ubuntu 上 Fortran 网络编程实操指南

一、环境准备与总体思路

  • 安装编译器与常用库:
    • 编译器:sudo apt update && sudo apt install gfortran
    • HTTP 等协议:sudo apt install libcurl4-openssl-dev
  • 实现路径:
    • 原生套接字:通过 ISO_C_BINDING 调用 C 的 socket API(socket/bind/listen/accept/send/recv)实现 TCP/UDP
    • 高级库:使用 libcurl 发起 HTTP/HTTPS/FTP 等请求,开发效率高、可移植性好。

二、方式一 原生套接字 TCP 最小示例(ISO_C_BINDING 调用 C API)

  • 思路要点
    • c_int、c_char、c_void、c_socklen_t 等 C 互操作类型;用 c_loc 传结构体指针;用 sizeof 取大小;字符串需以 C_NULL_CHAR 结尾;所有系统调用需检查返回值。
  • 服务器端 server.f90(IPv4/TCP,监听 12345
program server
  use, intrinsic :: iso_c_binding
  implicit none
  integer(c_int), parameter :: AF_INET = 2, SOCK_STREAM = 1, INADDR_ANY = 0
  integer(c_int), parameter :: PORT = 12345
  integer(c_int) :: server_fd, client_fd, n
  type(c_ptr) :: addr_ptr
  character(len=1), target :: addr_buf(16 + 2*4)  ! 简化:容纳 sockaddr_in
  integer(c_socklen_t) :: addr_len
  character(len=1024) :: buf

  interface
    function socket(domain, type, protocol) bind(c, name="socket")
      import c_int
      integer(c_int), value :: domain, type, protocol
      integer(c_int) :: socket
    end function socket
    function bind(sockfd, addr, addrlen) bind(c, name="bind")
      import c_int, c_ptr, c_socklen_t
      integer(c_int), value :: sockfd
      type(c_ptr), value :: addr
      integer(c_socklen_t), value :: addrlen
      integer(c_int) :: bind
    end function bind
    function listen(sockfd, backlog) bind(c, name="listen")
      import c_int
      integer(c_int), value :: sockfd, backlog
      integer(c_int) :: listen
    end function listen
    function accept(sockfd, addr, addrlen) bind(c, name="accept")
      import c_int, c_ptr, c_socklen_t
      integer(c_int), value :: sockfd
      type(c_ptr), value :: addr
      integer(c_socklen_t), intent(inout) :: addrlen
      integer(c_int) :: accept
    end function accept
    function recv(sockfd, buf, len, flags) bind(c, name="recv")
      import c_int, c_void, c_socklen_t
      integer(c_int), value :: sockfd
      type(c_ptr), value :: buf
      integer(c_socklen_t), value :: len
      integer(c_int), value :: flags
      integer(c_int) :: recv
    end function recv
    function close(fd) bind(c, name="close")
      import c_int
      integer(c_int), value :: fd
      integer(c_int) :: close
    end function close
  end interface

  ! 创建套接字
  server_fd = socket(AF_INET, SOCK_STREAM, 0_c_int)
  if (server_fd < 0) stop 'socket failed'

  ! 准备地址(简化:直接把 sockaddr_in 放在 addr_buf 中,端口用 htons)
  addr_buf = 0
  call set_sin_family(addr_buf, AF_INET)
  call set_sin_port  (addr_buf, htons(PORT))
  call set_sin_addr  (addr_buf, INADDR_ANY)

  addr_ptr = c_loc(addr_buf)
  addr_len = 16 + 2*4  ! sizeof(struct sockaddr_in)

  ! 绑定、监听、接收
  if (bind(server_fd, addr_ptr, addr_len) < 0) stop 'bind failed'
  if (listen(server_fd, 5_c_int) < 0) stop 'listen failed'
  print '("Server listening on port ", I0)', PORT

  client_fd = accept(server_fd, addr_ptr, addr_len)
  if (client_fd < 0) stop 'accept failed'
  print *, 'Client connected'

  n = recv(client_fd, c_loc(buf), int(size(buf), c_socklen_t), 0_c_int)
  if (n > 0) print '("Received: ", A)', trim(buf(:n))

  call close(client_fd)
  call close(server_fd)
contains
  subroutine set_sin_family(buf, fam)
    character(len=1), intent(inout) :: buf(*)
    integer(c_int), intent(in) :: fam
    buf(1:4) = transfer(fam, buf(1:4))
  end subroutine
  subroutine set_sin_port(buf, port)
    character(len=1), intent(inout) :: buf(*)
    integer(c_int), intent(in) :: port
    buf(5:8) = transfer(port, buf(5:8))
  end subroutine
  subroutine set_sin_addr(buf, addr)
    character(len=1), intent(inout) :: buf(*)
    integer(c_int), intent(in) :: addr
    buf(9:12) = transfer(addr, buf(9:12))
  end subroutine
end program server
  • 客户端 client.f90(连接 127.0.0.1:12345 并发送一行)
program client
  use, intrinsic :: iso_c_binding
  implicit none
  integer(c_int), parameter :: AF_INET = 2, SOCK_STREAM = 1
  integer(c_int), parameter :: PORT = 12345
  integer(c_int) :: sock, n
  type(c_ptr) :: addr_ptr
  character(len=1), target :: addr_buf(16 + 2*4)
  character(len=1024) :: msg = "Hello from Fortran client!" // c_null_char
  interface
    function socket(domain, type, protocol) bind(c, name="socket")
      import c_int
      integer(c_int), value :: domain, type, protocol
      integer(c_int) :: socket
    end function socket
    function connect(sockfd, addr, addrlen) bind(c, name="connect")
      import c_int, c_ptr, c_socklen_t
      integer(c_int), value :: sockfd
      type(c_ptr), value :: addr
      integer(c_socklen_t), value :: addrlen
      integer(c_int) :: connect
    end function connect
    function send(sockfd, buf, len, flags) bind(c, name="send")
      import c_int, c_void, c_socklen_t
      integer(c_int), value :: sockfd
      type(c_ptr), value :: buf
      integer(c_socklen_t), value :: len
      integer(c_int), value :: flags
      integer(c_int) :: send
    end function send
    function close(fd) bind(c, name="close")
      import c_int
      integer(c_int), value :: fd
      integer(c_int) :: close
    end function close
  end interface

  ! 创建套接字
  sock = socket(AF_INET, SOCK_STREAM, 0_c_int)
  if (sock < 0) stop 'socket failed'

  ! 准备地址(127.0.0.1)
  addr_buf = 0
  call set_sin_family(addr_buf, AF_INET)
  call set_sin_port  (addr_buf, htons(PORT))
  call set_sin_addr  (addr_buf, inet_addr("127.0.0.1"))

  addr_ptr = c_loc(addr_buf)
  if (connect(sock, addr_ptr, 16 + 2*4_c_socklen_t) < 0) stop 'connect failed'

  n = send(sock, c_loc(msg), int(len_trim(msg), c_socklen_t), 0_c_int)
  if (n < 0) print *, 'send failed'

  call close(sock)
contains
  subroutine set_sin_family(buf, fam)
    character(len=1), intent(inout) :: buf(*)
    integer(c_int), value :: fam
    buf(1:4) = transfer(fam, buf(1:4))
  end subroutine
  subroutine set_sin_port(buf, port)
    character(len=1), intent(inout) :: buf(*)
    integer(c_int), value :: port
    buf(5:8) = transfer(port, buf(5:8))
  end subroutine
  subroutine set_sin_addr(buf, addr)
    character(len=1), intent(inout) :: buf(*)
    integer(c_int), value :: addr
    buf(9:12) = transfer(addr, buf(9:12))
  end subroutine
end program client
  • 编译与运行
    • 编译:gfortran -o server server.f90gfortran -o client client.f90
    • 运行:终端1执行 ./server;终端2执行 ./client
  • 说明
    • 上述示例为教学简化版,直接把 sockaddr_in 放在连续字节缓冲中并通过 c_loc 传递;生产代码建议封装 struct sockaddr_in 的 C 互操作派生类型,更直观且不易出错。

三、方式二 使用 libcurl 发起 HTTP 请求

  • 安装库:sudo apt install libcurl4-openssl-dev
  • 示例 http_get.f90(HTTP GET 并打印错误码)
program http_get
  use, intrinsic :: iso_c_binding
  implicit none
  interface
    function curl_easy_init() bind(c, name="curl_easy_init")
      import c_ptr
      type(c_ptr) :: curl_easy_init
    end function curl_easy_init
    function curl_easy_setopt(curl, opt, param) bind(c, name="curl_easy_setopt")
      import c_ptr, c_int
      type(c_ptr), value :: curl
      integer(c_int), value :: opt
      type(c_ptr), value :: param
      integer(c_int) :: curl_easy_setopt
    end function curl_easy_setopt
    function curl_easy_perform(curl) bind(c, name="curl_easy_perform")
      import c_ptr, c_int
      type(c_ptr), value :: curl
      integer(c_int) :: curl_easy_perform
    end function curl_easy_perform
    subroutine curl_easy_cleanup(curl) bind(c, name="curl_easy_cleanup")
      import c_ptr
      type(c_ptr), value :: curl
    end subroutine curl_easy_cleanup
    function curl_easy_strerror(code) bind(c, name="curl_easy_strerror")
      import c_ptr, c_int
      integer(c_int), value :: code
      type(c_ptr) :: curl_easy_strerror
    end interface

  type(c_ptr) :: curl
  integer(c_int) :: res
  character(len=:), allocatable :: url
  character(len=256) :: errbuf

  curl = curl_easy_init()
  if (.not. c_associated(curl)) then
    print *, "curl_easy_init failed"
    stop 1
  end if

  url = "http://example.com" // c_null_char
  call curl_easy_setopt(curl, 10002_c_int, c_loc(url))        ! CURLOPT_URL
  call curl_easy_setopt(curl, 52_c_int,  1_c_int)           ! CURLOPT_FOLLOWLOCATION
  call curl_easy_setopt(curl, 199_c_int, c_loc(errbuf))      ! CURLOPT_ERRORBUFFER

  res = curl_easy_perform(curl)
  if (res /= 0) then
    print '("curl_easy_perform failed: ", A)', trim(errbuf)
  end if

  call curl_easy_cleanup(curl)
end program http_get
  • 编译与运行
    • 编译:gfortran -o http_get http_get.f90 -lcurl
    • 运行:./http_get
  • 提示
    • 若需要 HTTPS,安装 libcurl4-openssl-dev 即可;如需更详细错误信息,可使用 curl_easy_strerror 获取字符串描述。

四、编译链接与运行要点

  • 套接字示例通常只需 gfortran 源文件 即可编译;若使用 libcurl,务必加上 -lcurl 链接选项。
  • 运行前确认防火墙放行端口(示例为 12345/tcp):sudo ufw allow 12345/tcp
  • 所有系统调用与库函数调用都应检查返回值并做错误处理(如端口占用、连接失败、解析失败等)。

五、常见坑与优化建议

  • 字符与缓冲区
    • C 字符串需以 C_NULL_CHAR 结尾;Fortran 固定长度字符缓冲区接收数据后,用 len_trim 或记录实际 recv 字节数处理。
  • 字节序与地址
    • 端口需使用 htons 转为网络字节序;IPv4 地址可用 inet_addr(“127.0.0.1”) 得到网络序整数。
  • 结构体与指针
    • 生产代码建议为 struct sockaddr_in 定义明确的 C 互操作类型,避免手工填充字节缓冲带来的可维护性问题。
  • 并发与扩展
    • 高并发可结合 多进程/多线程 或并行库(如 OpenCoarrays)与 socket 协同设计;跨协议与跨平台需求优先考虑 libcurl 等成熟库。

0