Fortranでソケットを使いたい!
はじめに
私は少し前から天邪鬼にとりつかれているので、最近流行りの C ではなく Fortran を勉強しています。
Fortran でソケットを扱うプログラムを調べると、C で書かれたコードが付属していることが殆どです。Fortran はなぜか未だにソケットを扱うための標準ライブラリがないのでこれは当然のことですが、Fortran が不完全な言語と言われているようで悔しいですよね。なので、直接 C を書かずにソケットを扱うことを試みます。
対象
- Fortran 90 (またはそれ以降) の基礎的な文法を理解している人
- C でソケットを使ったことがある人
使うもの
今回のミソは INTERFACE (引用仕様宣言) と iso_c_binding モジュールです。INTERFACE は、他のファイルに分割された外部手続き(サブルーチンや関数)を使うために、引数や返り値の型を記述するための構文です。iso_c_binding は C と Fortran の相互利用をするための型や手続きを含む Fortran 2003 の標準モジュールです。つまり、今回の記事は Fortran 2003 以降を対象としています。Fortran 95 以前の規格にしか対応していないコンパイラを使う場合には今回の方法は使えません。他の方法で頑張ってください。
手順
モジュールをつくる
保守性の観点から、ソケットに関する定数や手続きはモジュールにまとめるようにします。
module mod_socket
use, intrinsic :: iso_c_binding
implicit none
interface
! ここに C 関数の引用仕様を書いていきます。
end interface
end module
ソケットを扱うのに必要な C 関数の仕様をひたすら書く
C 標準ライブラリのヘッダファイルを読みながら書いていきます。各関数には iso_c_binding の利用宣言が必要です。implicit none
を書くのは強く推奨されます。返り値のない C 関数はサブルーチン、返り値がある C の関数は関数として宣言します。
試しに socket()
関数の仕様を追加してみましょう。ヘッダファイルでの socket()
関数の定義は以下のとおりです。
int socket(int domain, int type, int protocol);
Fortran で C の int
に対応する型は INTEGER(c_int)
です。Fortran では引数は参照渡しですが、C では値渡しであるので、基本的に全ての引数には value
属性をつけます。また、BIND(C)
属性が必要です。Fortran の手続き名と C の関数名を変える場合は BIND(C, NAME=Cの関数名)
のようにします。Fortran の手続きなどと名前がかぶらないようにしましょう。
module mod_socket
use, intrinsic :: iso_c_binding
implicit none
interface
+ function c_socket(domain, type, protocol) bind(c, name="socket")
+ use, intrinsic :: iso_c_binding
+ implicit none
+ integer(c_int) :: c_socket
+ integer(c_int), value :: domain, type, protocol
+ end function
end interface
end module
どんどん書いていきます。bind()
関数を追加します。ポインタを渡す引数は全部 TYPE(c_ptr)
で宣言してしまいましょう。typedef
や #define
のことを Fortran は知りませんから、これらは定義を探して置き換えます。
! int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
function c_bind(sockfd, addr, addrlen) bind(c, name="bind")
use, intrinsic :: iso_c_binding
integer(c_int) :: c_bind
integer(c_int), value :: sockfd
type(c_ptr), value :: addr
integer(c_size_t), value :: addrlen
end function
この調子で、listen()
, connect()
, send()
, accept()
, send()
, recv()
, accept()
を追加しましょう。以下に型の対応表を置いておきます。
C | Fortran |
---|---|
int | INTEGER(c_int) |
long | INTEGER(c_long) |
short | INTEGER(c_short) |
(数値として扱いたい) char | INTEGER(c_signed_char) |
(文字として扱いたい) char | CHARACTER(c_char) |
size_t | INTEGER(c_size_t) |
sockaddr* | TYPE(c_ptr) |
socklen* | TYPE(c_ptr) |
sockaddr構造体を定義
できましたか?ところで、sockaddr 構造体のポインタを要求する関数がいました。多くの場合 sockaddr_in 構造体で充分なはずですので、sockaddr_in 構造体を実装します。Fortran で構造体は派生型と呼ばれ、TYPE
~ END TYPE
で定義します。このとき、C 構造体と互換性をもたせるために BIND(C)
属性が必要です。
module mod_socket
use, intrinsic :: iso_c_binding
implicit none
+ type, bind(c) :: sockaddr_in
+ integer(c_short) :: sin_family, sin_port
+ integer(c_int) :: sin_addr
+ character(c_char) :: sin_zero(8)
+ end type sockaddr_in
+
interface
! 長いので省略
end interface
end module
足りない関数を追加
C では socket()
で開いたソケットは close()
を使って閉じます。しかし、Fortran の close()
はファイルディスクリプタではなく装置番号を引数にとるため、別で C の close()
を使えるようにしましょう。他にもNull終端文字列の長さを取得したり、比較をするための strlen()
、strcmp()
と、バイトオーダー変換のための htons()
、htonl()
が必要になるので、同様に引用仕様宣言を追加しましょう。後の4つの関数は頑張れば Fortran 単体でも作れるかもしれません。
SOCK_STREAM とかを定数として追加
なくてもいいですが、わかりやすくするためです。
module mod_socket
use, intrinsic :: iso_c_binding
implicit none
+ ! Address Families
+ integer(c_int), parameter :: AF_INET = 2
+ integer(c_int), parameter :: AF_INET6 = 10
+
+ ! Type of Sockets
+ integer(c_int), parameter :: SOCK_STREAM = 1
+ integer(c_int), parameter :: SOCK_DGRAM = 2
+
+
+ integer(c_int), parameter :: INADDR_ANY = 0
+
type, bind(c) :: sockaddr_in
integer(c_short) :: sin_family, sin_port
integer(c_int) :: sin_addr
character(c_char) :: sin_zero(8)
end type sockaddr_in
interface
! 長いので省略
end interface
end module
完成
だいたいこんな感じになります。(いろいろ変えているので違うところもあるかも)
scrwnl/mod_socket - GitHub
動かしてみましょう
C の'\r'
や'\n'
は Fortran では iso_c_binding を利用したうえで、それぞれ c_carriage_return
と c_newline
という名前の定数を用い、//
で連結します。また、TYPE(c_ptr)
を渡す引数には、C_LOC(X)
という関数にtarget属性がついた変数を渡すことでメモリ上の位置を得られるので、その値を渡します。文字列変数の末尾には、c_null_char
を //
で追加しましょう。それ以外はだいたいCと同じです。
コンパイルするとき、mod_socket.f90を先にコンパイルし、mod_socket.modを生成する必要があります。
$ gfortran -c mod_socket.f90
$ gfortran main.f90 mod_socket.o
Hello, World!
ブラウザで接続すると、Hello, World!を表示します。簡単のために同期的に処理しています。
program hello
use, intrinsic :: iso_c_binding
use mod_socket
implicit none
integer(c_int) :: lsock, asock, status
type(sockaddr_in), target :: addr, client
character(len=2), parameter :: crlf = c_carriage_return // c_new_line
character(len=1024), target :: response = &
"HTTP/1.1 200 OK" // crlf // &
"Server: Fortran HTTP Server (Linux)" // crlf // &
"Content-Type: text/html" // crlf // &
"" // crlf // &
"<h1>Hello, World!</h1>" // crlf // c_null_char
integer(c_size_t) :: response_len
lsock = c_socket(AF_INET, SOCK_STREAM, 0)
if (lsock .lt. 0) then
print*,"Error: Cannot make socket."
stop 1
end if
status = c_memset(c_loc(addr), 0, sizeof(addr))
addr%sin_family = AF_INET
addr%sin_port = c_htons(8080)
addr%sin_addr = c_htonl(INADDR_ANY)
status = c_bind(lsock, c_loc(addr), sizeof(addr))
if (status .lt. 0) then
print*, "Error: Cannot bind socket"
stop 1
end if
status = c_listen(lsock, 5)
do
asock = c_accept(lsock, c_null_ptr, c_null_ptr)
response_len = c_strlen(c_loc(response))
print*, "送信: Hello World!"
status = c_send(asock, c_loc(response), response_len, 0)
status = c_close(asock)
enddo
status = c_close(lsock)
end program
インチキ時計
ブラウザに対してインチキHTMLを送りつけます。簡単のために同期的に処理しています。
program clock
use, intrinsic :: iso_c_binding
use mod_socket
implicit none
integer(c_int) :: lsock, asock, status
type(sockaddr_in), target :: addr, client
character(len=2), parameter :: crlf = c_carriage_return // c_new_line
character(:), allocatable, target :: response
integer(c_size_t) :: response_len
lsock = c_socket(AF_INET, SOCK_STREAM, 0)
if (lsock .lt. 0) then
print*,"Error: Cannot make socket."
stop 1
end if
status = c_memset(c_loc(addr), 0, sizeof(addr))
addr%sin_family = AF_INET
addr%sin_port = c_htons(8080)
addr%sin_addr = c_htonl(INADDR_ANY)
status = c_bind(lsock, c_loc(addr), sizeof(addr))
if (status .lt. 0) then
print*, "Error: Cannot bind socket"
stop 1
end if
status = c_listen(lsock, 5)
do
asock = c_accept(lsock, c_null_ptr, c_null_ptr)
response = &
"HTTP/1.1 200 OK" // crlf // &
"Server: Fortran HTTP Server" // crlf // &
"Content-Type: text/html; charset=utf-8" // crlf // &
"" // crlf // &
"<!DOCTYPE html>" // crlf // &
"<meta charset=utf-8>" // crlf // &
"<title>The Weird Clock</title>" // crlf // &
"<meta http-equiv=refresh content='1'>" // crlf // &
"<h1>Date: " // ctime(time()) // "</h1>" // crlf // c_null_char
response_len = c_strlen(c_loc(response))
print*, "===== RESPONSE ====="
print*, response
status = c_send(asock, c_loc(response), response_len, 0)
status = c_close(asock)
enddo
status = c_close(lsock)
end program weird_clock
小技
C の関数は返り値を利用せず捨てることができますが、Fortran ではできないようなので (変数に代入したり、式に組み込む必要がある) 、返り値をローカル変数に代入して引数として返すサブルーチンで包むといいかもしれません。その場合、返り値を代入させる変数は OPTIONALな引数で渡すとよさそうです。
おわりに
引用仕様宣言を活用してソケットを扱うことができました。必要に応じて fork()
などを追加することで、マルチプロセスな HTTP サーバーなども作れるようになります。決して Fortran は負けておりません。みなさんもぜひ Fortran で実用アプリケーションを書いてみてくださいね。
参考文献
- 日本ニューメリカルアルゴリズムズグループ株式会社. Fortran Tip集: C との相互利用可能性の例. 日本ニューメリカルアルゴリズムズグループ株式会社. (Accessed 2025-08-08)
- 日本ニューメリカルアルゴリズムズグループ株式会社. Fortran 2003入門: ISO_C_BINDING モジュール. 日本ニューメリカルアルゴリズムズグループ株式会社. (Accessed 2025-08-08)
- ph-preux (Philippe). ph-preux/sockets-in-Fortran. GitHub. 2020-04-06. (Accessed 2025-08-08)
Discussion