3

REPLで最小限の TCP サーバーを実行していSML/NJますが、キーボード割り込みでリスナー ソケットを適切に閉じる方法を知りたいと思っています。サーバーの簡素化されたバージョンは

fun sendHello sock = 
    let val res = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello world!\r\n\r\n"
        val slc = Word8VectorSlice.full (Byte.stringToBytes res)
    in 
      Socket.sendVec (sock, slc);
      Socket.close sock
    end

fun acceptLoop serv =
    let val (s, _) = Socket.accept serv
    in print "Accepted a connection...\n";
       sendHello s;
       acceptLoop serv
    end

fun serve port =
    let val s = INetSock.TCP.socket()
    in Socket.Ctl.setREUSEADDR (s, true);
       Socket.bind(s, INetSock.any port);
       Socket.listen(s, 5);
       print "Entering accept loop...\n";
       acceptLoop s
    end

問題は、ポートでリッスンしているこのサーバーを起動し、キーボード割り込みでキャンセルしてから、同じポートで再起動しようとすると、エラーが発生することです。

Standard ML of New Jersey v110.76 [built: Thu Feb 19 00:37:13 2015]
- use "test.sml" ;;
[opening test.sml]
[autoloading]
[library $SMLNJ-BASIS/basis.cm is stable]
[autoloading done]
val sendHello = fn : ('a,Socket.active Socket.stream) Socket.sock -> unit
val acceptLoop = fn : ('a,Socket.passive Socket.stream) Socket.sock -> 'b
val serve = fn : int -> 'a
val it = () : unit
- serve 8181 ;;
stdIn:2.1-2.11 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)
Entering accept loop...
Accepted a connection...
  C-c C-c
Interrupt
- serve 8181 ;;
stdIn:1.2-1.12 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)

uncaught exception SysErr [SysErr: Address already in use [<UNKNOWN>]]
  raised at: <bind.c>
- 

そのため、エラーが発生したときにリッスン ソケットを閉じられるようにしたいと考えています。Interruptキーボード割り込みを発行するとREPLに表示されるので、それInterruptがキャッチする予定の例外のコンストラクターであると想定しました。ただし、適切なhandle行をacceptLooporに追加serveしても、私が望むことはできないようです。

fun acceptLoop serv =
    let val (s, _) = Socket.accept serv
    in print "Accepted a connection...\n";
       sendHello s;
       acceptLoop serv
    end
    handle Interrupt => Socket.close serv

fun serve port =
    let val s = INetSock.TCP.socket()
    in Socket.Ctl.setREUSEADDR (s, true);
       Socket.bind(s, INetSock.any port);
       Socket.listen(s, 5);
       print "Entering accept loop...\n";
       acceptLoop s
       handle Interrupt => Socket.close s
    end

(その後、REPLで)

- use "test.sml" ;;
[opening test.sml]
val sendHello = fn : ('a,Socket.active Socket.stream) Socket.sock -> unit
val acceptLoop = fn : ('a,Socket.passive Socket.stream) Socket.sock -> 'b
val serve = fn : int -> 'a
val it = () : unit
- serve 8182 ;;
stdIn:3.1-3.11 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)
Entering accept loop...
Accepted a connection...
  C-c C-c
Interrupt
- serve 8182 ;;
stdIn:1.2-1.12 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)

uncaught exception SysErr [SysErr: Address already in use [<UNKNOWN>]]
  raised at: <bind.c>
- 

変数 ( handle x => (Socket.close s; raise x)) またはワイルドカード ( handle _ => Socket.close s) 例外一致で同じことを行うと、上記と同じ効果があります。

4

1 に答える 1

1

標準 ML 自体にはかなり大きな制限があります。つまり、標準言語には並行プログラミングの準備が整っていないということです。そして、この特定のケースでは並行性が必要です。

幸いなことに、SML/NJ を使用しています。これには、同時実行サポートを可能にするいくつかの拡張機能 (継続) があります。

SML/NJ では、割り込みハンドラをインストールしてから、必要なプログラムの継続を再開できます。関数は次のようになりますserve(私は SML/NJ での継続に関しては初心者なので、「これがあなたのやり方だ」というよりはむしろヒントです):

fun serve port =
  (*
   * Capture the current continuation, which is basically the next REPL
   * prompt after the server is done accepting requests.
   *)
  SMLofNJ.Cont.callcc (fn serverShutdownCont =>
    let
      val s = INetSock.TCP.socket()

      (*
       * The interrupt handler that is called when ^C is pressed.
       * Shuts down the server and returns the continuation that should
       * be resumed next, i.e. `serverShutdownCont`.
       *)
      fun interruptHandler (signal, n, cont) =
        let in
          print "Shutting down server... "
        ; Socket.close s
        ; print "done.\n"
        ; serverShutdownCont
        end
    in
      (* Register the interrupt handler. *)
      Signals.setHandler (Signals.sigINT, Signals.HANDLER interruptHandler);
      Socket.Ctl.setREUSEADDR (s, true);
      Socket.bind(s, INetSock.any port);
      Socket.listen(s, 5);
      print "Entering accept loop...\n";
      acceptLoop s
    end)

これについて詳しく知るための非常に優れたリソースは、Unix System Programming with Standard MLです。ここでは小さな Web サーバーが開発されているため、おそらく非常に役立つでしょう。

途中で遭遇するもう 1 つのことは、accept ループでの同時実行です。現在、プログラムは一度に 1 つの HTTP リクエストしか処理できません。必ずしも並列ではなく、少なくとも同時に (インターリーブされた) 一度により多くをサポートしたい場合は、並列ML (CML)を調べる必要があります。これは、標準 ML の同時拡張であり、ライブラリとして実装されています。 SML/NJ が提供する継続の上に。CML は SML/NJ に同梱されています。

ライブラリの作成者である John Reppy によって書かれた CML に関する非常に優れたチュートリアルはConcurrent Programming in MLです。私は最近、この本の最初の部分に取り組みましたが、非常に徹底的に説明されています。

于 2015-06-08T09:17:35.040 に答える