在此Common Lisp示例中,套接字关闭的目的是什么?

时间:2020-03-21 19:18:32

标签: common-lisp usocket

我从Common Lisp Cookbook中发现了this example,其中展示了如何使用usocket启动TCP服务器。

该示例创建一个套接字对象并建立连接,然后将其写入套接字。如果发生错误,套接字写操作将被包装在一个展开保护中,该保护将关闭套接字,以便可以重复使用。我已经重写了导致错误的示例,但是当我多次运行该示例时,我得到了USOCKET:ADDRESS-IN-USE-ERROR。如果删除socket-close函数调用,则行为相同。

(load "~/quicklisp/setup.lisp")
(ql:quickload "usocket")

(let* ((socket (usocket:socket-listen "localhost" 8080))
       (connection (usocket:socket-accept socket)))
        (unwind-protect
          (progn
            (error "error 1"))
          (progn
            (usocket:socket-close connection)
            (usocket:socket-close socket)
            (print "Error clean up"))))
Unhandled USOCKET:ADDRESS-IN-USE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                                    {10005E85B3}>:
  Condition USOCKET:ADDRESS-IN-USE-ERROR was signalled.

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {10005E85B3}>
0: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<USOCKET:ADDRESS-IN-USE-ERROR {1003FDC063}> #<unused argument> :QUIT T)
1: (SB-DEBUG::RUN-HOOK *INVOKE-DEBUGGER-HOOK* #<USOCKET:ADDRESS-IN-USE-ERROR {1003FDC063}>)
2: (INVOKE-DEBUGGER #<USOCKET:ADDRESS-IN-USE-ERROR {1003FDC063}>)
3: (ERROR #<USOCKET:ADDRESS-IN-USE-ERROR {1003FDC063}>)
4: (USOCKET:SOCKET-LISTEN "localhost" 8080 :REUSEADDRESS NIL :REUSE-ADDRESS NIL :BACKLOG 5 :ELEMENT-TYPE CHARACTER)
5: ((LAMBDA NIL :IN "/home/sam/test/serve.lisp"))
6: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET* ((SOCKET (USOCKET:SOCKET-LISTEN "localhost" 8080)) (CONNECTION (USOCKET:SOCKET-ACCEPT SOCKET))) (UNWIND-PROTECT (PROGN (ERROR "error 1")) (PROGN (USOCKET:SOCKET-CLOSE CONNECTION) (USOCKET:SOCKET-CLOSE SOCKET) (PRINT "Error clean up")))) #<NULL-LEXENV>)
7: (EVAL-TLF (LET* ((SOCKET (USOCKET:SOCKET-LISTEN "localhost" 8080)) (CONNECTION (USOCKET:SOCKET-ACCEPT SOCKET))) (UNWIND-PROTECT (PROGN (ERROR "error 1")) (PROGN (USOCKET:SOCKET-CLOSE CONNECTION) (USOCKET:SOCKET-CLOSE SOCKET) (PRINT "Error clean up")))) 2 NIL)
8: ((LABELS SB-FASL::EVAL-FORM :IN SB-INT:LOAD-AS-SOURCE) (LET* ((SOCKET (USOCKET:SOCKET-LISTEN "localhost" 8080)) (CONNECTION (USOCKET:SOCKET-ACCEPT SOCKET))) (UNWIND-PROTECT (PROGN (ERROR "error 1")) (PROGN (USOCKET:SOCKET-CLOSE CONNECTION) (USOCKET:SOCKET-CLOSE SOCKET) (PRINT "Error clean up")))) 2)
9: ((LAMBDA (SB-KERNEL:FORM &KEY :CURRENT-INDEX &ALLOW-OTHER-KEYS) :IN SB-INT:LOAD-AS-SOURCE) (LET* ((SOCKET (USOCKET:SOCKET-LISTEN "localhost" 8080)) (CONNECTION (USOCKET:SOCKET-ACCEPT SOCKET))) (UNWIND-PROTECT (PROGN (ERROR "error 1")) (PROGN (USOCKET:SOCKET-CLOSE CONNECTION) (USOCKET:SOCKET-CLOSE SOCKET) (PRINT "Error clean up")))) :CURRENT-INDEX 2)
10: (SB-C::%DO-FORMS-FROM-INFO #<CLOSURE (LAMBDA (SB-KERNEL:FORM &KEY :CURRENT-INDEX &ALLOW-OTHER-KEYS) :IN SB-INT:LOAD-AS-SOURCE) {1001B7128B}> #<SB-C::SOURCE-INFO {1001B71243}> SB-C::INPUT-ERROR-IN-LOAD)
11: (SB-INT:LOAD-AS-SOURCE #<SB-SYS:FD-STREAM for "file /home/sam/test/serve.lisp" {1001B66763}> :VERBOSE NIL :PRINT NIL :CONTEXT "loading")
12: ((FLET SB-FASL::THUNK :IN LOAD))
13: (SB-FASL::CALL-WITH-LOAD-BINDINGS #<CLOSURE (FLET SB-FASL::THUNK :IN LOAD) {7FFFF63E769B}> #<SB-SYS:FD-STREAM for "file /home/sam/test/serve.lisp" {1001B66763}>)
14: ((FLET SB-FASL::LOAD-STREAM :IN LOAD) #<SB-SYS:FD-STREAM for "file /home/sam/test/serve.lisp" {1001B66763}> NIL)
15: (LOAD #<SB-SYS:FD-STREAM for "file /home/sam/test/serve.lisp" {1001B66763}> :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST T :EXTERNAL-FORMAT :DEFAULT)
16: ((FLET SB-IMPL::LOAD-SCRIPT :IN SB-IMPL::PROCESS-SCRIPT) #<SB-SYS:FD-STREAM for "file /home/sam/test/serve.lisp" {1001B66763}>)
17: ((FLET SB-UNIX::BODY :IN SB-IMPL::PROCESS-SCRIPT))
18: ((FLET "WITHOUT-INTERRUPTS-BODY-3" :IN SB-IMPL::PROCESS-SCRIPT))
19: (SB-IMPL::PROCESS-SCRIPT "serve.lisp")
20: (SB-IMPL::TOPLEVEL-INIT)
21: ((FLET SB-UNIX::BODY :IN SAVE-LISP-AND-DIE))
22: ((FLET "WITHOUT-INTERRUPTS-BODY-36" :IN SAVE-LISP-AND-DIE))
23: ((LABELS SB-IMPL::RESTART-LISP :IN SAVE-LISP-AND-DIE))

unhandled condition in --disable-debugger mode, quitting

1 个答案:

答案 0 :(得分:5)

您得到此消息的原因是由于TCP协议的性质:在RFC793描述的TCP状态机中,连接处于称为TIME-WAIT的状态。状态机的图示位于RFC793的page 23上。

状态机有趣的一点是,当一端(我将其称为“您”)要关闭连接时–这称为“活动关闭”,在这种情况下,这就是您要通过的方式发起的连接socket-close调用。我将另一端称为“他们”。主动关闭的正常事件顺序为:

  1. 您向他们发送了FIN数据包;
  2. 他们确认您的FIN并依次发送FIN;
  3. 您确认他们的FIN。

现在重要的是要记住,所有这些数据包(它们的ACK和FIN通常是同一数据包,我认为总是这样)可能会丢失,并且状态机需要从中恢复。

有一个特别有趣的数据包是最后一个ACK:它特别有趣,因为这是有史以来最后一个发送的数据包,这意味着您无法知道它是否到达了它们

所以要从两端考虑情况

从他们的结尾:他们已经发送了FIN,并正在等待您的确认。现在:

  • 两个ACK都在适当的时候到达,在这种情况下,他们知道一切都已经结束,可以拆除连接中涉及的所有东西。
  • ,或者在等待了规定的时间后,ACK 没有到达,因此他们必须假设自己的FIN丢失或您对FIN的ACK丢失了,因此必须重新发送FIN,然后返回以等待ACK。

从头开始:您已经获得了他们的FIN并已发送了最后一个ACK。但是您不知道该ACK是否曾经出现在他们身上。因此,您等待指定的时间,以便有机会让他们认识到ACK没有得到他们并重新发送他们的FIN。在此等待期间,您无法拆除连接,因为在任何时候您都可能会获得另一个FIN。

此等待状态称为TIME-WAIT,在此期间无法重用连接的端点。这就是您遇到的问题。

您需要在TIME-WAIT中等待最长段寿命(MSL)的两倍:MSL是指数据包在网络中的等待时间。

如果更早的数据包丢失,当然还有其他等待状态可能在TIME-WAIT之前发生。但是TIME-WAIT是唯一总是发生的时间。

由于语言无法处理名称中的连字符,因此TIME-WAIT通常称为TIME_WAIT。