处理键盘中断
Handling Keyboard Interrupts
我在 SML/NJ
REPL 中有一个最小的 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
行添加到 acceptLoop
或 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
) 异常匹配执行相同的操作与上面的效果相同。
您遇到了标准 ML 本身的一个相当大的限制,即标准语言没有为并发编程做出任何规定。在这种特殊情况下,您需要并发。
幸运的是,您正在使用 SML/NJ,它有一些允许并发支持的扩展 — continuations。
在SML/NJ中,您可以install an interrupt handler然后继续您想要的任何程序继续。这是您的 serve
函数的样子(我自己也是 SML/NJ 中的延续的初学者,所以这更像是一个提示,而不是 "this is how you do it" 示例):
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。我最近读完了这本书的第一部分,解释得非常透彻。
我在 SML/NJ
REPL 中有一个最小的 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
行添加到 acceptLoop
或 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
) 异常匹配执行相同的操作与上面的效果相同。
您遇到了标准 ML 本身的一个相当大的限制,即标准语言没有为并发编程做出任何规定。在这种特殊情况下,您需要并发。
幸运的是,您正在使用 SML/NJ,它有一些允许并发支持的扩展 — continuations。
在SML/NJ中,您可以install an interrupt handler然后继续您想要的任何程序继续。这是您的 serve
函数的样子(我自己也是 SML/NJ 中的延续的初学者,所以这更像是一个提示,而不是 "this is how you do it" 示例):
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。我最近读完了这本书的第一部分,解释得非常透彻。