SMLでSocketプログラミング

SMLのBasisライブラリにはBSD-Socketのインターフェースが含まれている*1 ので使ってみました.


以下にサーバ側で待ち受けるソケットを作る箇所を示します.

structure S = Socket
structure N = INetSock
infix >>=
fun op>>= (NONE  ,_) = NONE
  | op>>= (SOME s,f) = f s
fun makeAddr (host:string) (port:int) : N.sock_addr option =
  let
    val addr = (NetHostDB.getByName host) >>= (fn addr =>
  			   SOME(N.toAddr(NetHostDB.addr addr, port)))
  in addr
  end

fun server_socket (host:string) (port:int) : S.passive N.stream_sock option =
  let
    val addr = makeAddr host port
    val sock = N.TCP.socket()
  in
    addr >>= (fn addr' =>
    (S.Ctl.setREUSEADDR (sock, true) (* SML/njだと不調? *)
    ;S.bind (sock, addr') 
    ;S.listen (sock, 5)
    ;SOME sock))
  end

unitだらけで非常に気持ち悪いですね. うへぇ….


続いて,エコーサーバの実行

fun echo_server sock = let
  val s = #1 (accept sock)
  fun loop () = let
    val recv_ = recvVec(s, 256)
  in
  	if Word8Vector.length(recv_)=0 then SOME s
	else loop() before ignore(sendVec(s, Word8VectorSlice.full recv_))
  end
in loop () handle SysErr => NONE
end
val _ = (server_socket "localhost" 2000) >>= echo_server >>= close_socket

非常に簡潔です.

まとめ

  • ほぼC言語で扱うソケットまんまなワケですが, かなり複雑に型付けされているので, 何らかのラッパーが必要だと考えています.
    • 使うだけなら型を合わせれば何となく動いちゃうのが凄いわけですが….
  • bind(>>=の方)が便利すぎですね
  • Socket.Ctl.setREUSEADDR(sock, true) (C言語では(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &opt, opt_len))に相当) がSML/njのインタプリタ上だとうまく動いてないように見えます. 要注意.

*1:例によってオプショナルな扱いなワケですが…