处理键盘中断

3

我在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是我需要捕获的异常的构造函数。但是,在acceptLoop或者serve中添加适当的handle行似乎不能达到我想要的效果。

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)来捕获异常,与上述方法具有相同的效果。

1个回答

1
你已经遇到了 Standard ML 本身的一个很大限制,即标准语言没有为并发编程做出任何规定。而在这个特定情况下,你需要并发性。
幸运的是,你正在使用 SML/NJ,它具有一些扩展,允许并发支持- continuations
在 SML/NJ 中,你可以 安装中断处理程序,然后恢复你想要的任何程序继续执行。下面是你的 serve 函数可能看起来像的方式(当涉及到 SML/NJ 中的 continuations 时,我本人也是初学者,所以这只是一个提示,而不是 "这就是你该怎么做" 的例子):
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),这是Standard ML的并发扩展,作为SML/NJ提供的连续性库的顶层实现。 CML与SML / NJ一起提供。
CML的一个非常好的教程,由该库的作者John Reppy编写,是Concurrent Programming in ML。我最近仔细阅读了本书的第一部分,它真的讲得非常透彻。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接