【问题标题】:Handling Keyboard Interrupts处理键盘中断
【发布时间】:2015-08-22 08:22:56
【问题描述】:

我在SML/NJREPL 中运行了一个最小的 TCP 服务器,我想知道如何在键盘中断时优雅地关闭侦听器套接字。服务器的精简版是

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>
- 

所以我希望能够在发生错误时关闭监听套接字。当我发出键盘中断时,我在 REPL 中看到 Interrupt,所以我假设 Interrupt 是我希望捕获的异常的构造函数。但是,将适当的 handle 行添加到 acceptLoopserve 似乎并没有达到我想要的效果。

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 =&gt; (Socket.close s; raise x)) 或通配符 (handle _ =&gt; Socket.close s) 异常匹配执行相同的操作具有与上述相同的效果。

【问题讨论】:

    标签: sockets exception-handling sml smlnj keyboardinterrupt


    【解决方案1】:

    您在使用标准 ML 本身时遇到了一个相当大的限制,即标准语言没有为并发编程做出任何规定。在这种特殊情况下,您需要并发。

    幸运的是,您使用的是 SML/NJ,它有一些允许并发支持的扩展 — continuations

    在 SML/NJ 中,您可以 install an interrupt handler 然后继续您想要的任何程序继续。以下是您的 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 服务器,因此您可能会发现它非常有用。

    您会遇到的另一件事是接受循环中的并发性。现在,您的程序一次只能处理一个 HTTP 请求。如果您想一次支持更多,不一定是并行的,但至少是同时的(交错的),那么您必须查看Concurrent ML (CML),它是标准 ML 的并发扩展,实现为顶部的库SML/NJ 提供的延续。 CML 与 SML/NJ 一起发货。

    库的作者 John Reppy 编写的关于 CML 的非常好的教程是 Concurrent Programming in ML。我最近通读了本书的第一部分,它的解释非常透彻。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-25
      • 2010-11-27
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多