TCP选项SO_LINGER(零) - 何时需要

dim*_*mba 84 sockets networking tcp setsockopt so-linger

我想我理解该选项的正式含义.在我正在处理的一些遗留代码中,使用了该选项.客户抱怨RST作为对其接近的连接的FIN的回应.

我不确定我是否可以安全地将其删除,因为我不明白何时应该使用它.

能否举例说明何时需要该选项?

mgd*_*mgd 171

对于我的建议,请阅读最后一节:"何时使用超时0的SO_LINGER".

在我们开始讨论之前:

  • 正常的TCP终止
  • TIME_WAIT
  • FIN,ACKRST

正常的TCP终止

正常的TCP终止序列看起来像这样(简化):

我们有两个同行:A和B.

  1. 一个电话 close()
    • A发送FIN给B
    • 进入FIN_WAIT_1状态
  2. B接收 FIN
    • B发送ACK给A
    • B进入CLOSE_WAIT
  3. 收到 ACK
    • 进入FIN_WAIT_2状态
  4. B来电 close()
    • B发送FIN给A
    • B进入LAST_ACK
  5. 收到 FIN
    • A发送ACK给B
    • 进入TIME_WAIT状态
  6. B接收 ACK
    • B进入CLOSED状态 - 即从套接字表中删除

时间的等待

因此,启动终止的对等体 - 即close()首先调用- 将最终处于该TIME_WAIT状态.

要了解TIME_WAIT州为什么是我们的朋友,请阅读Stevens等人的"UNIX网络编程"第三版第2.7节(第43页).

但是,它可能是TIME_WAIT服务器上状态很多套接字的问题,因为它最终可能会阻止接受新连接.

为了解决这个问题,我看到很多人建议在调用之前将超时0设置为SO_LINGER套接字选项close().但是,这是一个糟糕的解决方案,因为它会导致TCP连接因错误而终止.

相反,请设计应用程序协议,以便始终从客户端启动连接终止.如果客户端总是知道它何时读取了所有剩余数据,则它可以启动终止序列.例如,浏览器Content-Length在读取所有数据时从HTTP标头知道并且可以启动关闭.(我知道在HTTP 1.1中它会保持打开一段时间以便重用,然后关闭它.)

如果服务器需要关闭连接,请设计应用程序协议,以便服务器要求客户端进行呼叫close().

何时使用超时0的SO_LINGER

同样,根据"UNIX网络编程"第三版第202-203页,SO_LINGER在调用之前使用超时0进行设置close()将导致启动正常终止序列.

相反,设置此选项和调用的对等体close()将发送RST(连接重置),指示错误条件,这是在另一端感知它的方式.您通常会看到"按对等方重置连接"等错误.

因此,在正常情况下,在服务器应用程序中SO_LINGER调用之前设置超时0 close()(从现在开始称为abortive close)是一个非常糟糕的主意.

但是,某些情况保证这样做:

  • 如果您的服务器应用程序的客户端行为不正常(超时,返回无效数据等),则无意中关闭是有意义的,以避免陷入CLOSE_WAIT或最终处于该TIME_WAIT状态.
  • 如果必须重新启动当前具有数千个客户端连接的服务器应用程序,则可以考虑设置此套接字选项以避免使用数千个服务器套接字TIME_WAIT(close()从服务器端调用时),因为这可能会阻止服务器获取新客户端连接的可用端口重启后.
  • 在上述书中的第202页,它特别说:"在某些情况下,有必要使用此功能发送中断关闭.一个例子是RS-232终端服务器,它可能会永远挂起,CLOSE_WAIT试图将数据传送到卡住的终端端口,但如果RST要丢弃挂起的数据,将正确地重置卡住的端口."

我会推荐这篇长篇文章,我认为这篇文章能很好地回答你的问题.

  • `TIME_WAIT`只有在它没有引起问题时才是朋友:http://stackoverflow.com/questions/1803566/what-is-the-cost-of-many-time-wait-on-the-server -侧 (5认同)
  • 那么如果您正在编写Web服务器呢?您如何“告诉客户发起关闭”? (2认同)
  • @ShaunNeal您显然不知道。但是编写良好的客户端/浏览器将启动关闭。如果客户端的行为不正常,则幸运的是我们有TIME_WAIT暗杀,以确保我们不会用尽套接字描述符和临时端口。 (2认同)

caf*_*caf 72

SO_LINGER超时设置为零的典型原因是避免处于该TIME_WAIT状态的大量连接,从而占用服务器上的所有可用资源.

当干净地关闭TCP连接时,启动关闭的结束("主动关闭")最终会在连接处TIME_WAIT停留几分钟.因此,如果您的协议是服务器启动连接关闭的协议,并且涉及大量短期连接,那么它可能容易受到此问题的影响.

但这不是一个好主意 - TIME_WAIT存在的原因(确保来自旧连接的杂散数据包不会干扰新连接).如果可能的话,最好将协议重新设计为客户端启动连接的协议.

  • 我不同意.位于TCP之上的应用程序级协议应设计为客户端始终启动连接关闭.这样,`TIME_WAIT`将坐在客户端不受伤害.请记住它在"UNIX网络编程"第三版(Stevens等人)第203页中说:"TIME_WAIT状态是你的朋友并且在那里帮助我们.我们不应该试图避开状态,而应该理解它(第2.7节) ". (20认同)
  • 如果客户端想要每30秒打开4000个连接(这个监控应用程序是客户端!因为它启动了连接)怎么办?是的,我们可以重新设计应用程序,在基础架构中添加一些本地代理,将模型更改为推送.但是,如果我们已经拥有这样一个应用程序并且它已经增长,那么我们可以通过调整tw linger来使其工作.你改变了一个参数,你突然有了应用程序,没有投入预算来实现新的架构. (8认同)
  • @caf不,经典的解决方案是连接池,如每个重型TCP API中所见,例如HTTP 1.1. (7认同)
  • 我完全同意.我已经看到一个监控应用程序启动了许多(每X秒有几千个短期连接),并且它有更大的扩展(更多连接).我不知道为什么,但应用程序没有响应.有人建议SO_LINGER = true,TIME_WAIT = 0来快速释放操作系统资源,经过简短的调查后我们确实尝试了这个解决方案并取得了很好的效果.TIME_WAIT不再是这个应用程序的问题. (3认同)
  • 关闭SO_LINGER不会避免处于TIME_WAIT状态的大量连接. (3认同)
  • @JingguoYao正确.将其设置为零超时即可.不惜一切代价避免这种可行的做法. (3认同)
  • @ bartosz.r:我只是说使用超时0的SO_LINGER应该是最后的选择.同样,在"UNIX网络编程"第三版(Stevens等人)第203页中,它还表示您存在数据损坏的风险.考虑阅读RFC 1337,你可以看到为什么TIME_WAIT是你的朋友. (2认同)
  • @mgd你提出了一个很好的观点.人们必须知道所有这一切并在野外使用之前对其进行测试.我只能说在我的情况下有快速和短暂的传输,因此TCP/IP中的随机Seq编号(如RFC 1337中所述)应该关注旧数据包.至少我还没有看到任何腐败现象. (2认同)

Len*_*ate 16

当"间隔"打开但超时为零时,TCP堆栈不会等待在关闭连接之前发送待处理数据.由于这种情况,数据可能会丢失,但是通过这种方式设置逗留,您接受这个并要求立即重置连接而不是优雅地关闭.这导致发送RST而不是通常的FIN.

感谢EJP的评论,详情请见此处.

  • 每当你想中止连接; 因此,如果您的协议验证失败并且您有一个客户端突然对您说垃圾,您将中止与RST的连接等. (5认同)
  • 你将零余流超时与挥之不去的混乱混淆了.延迟关闭意味着close()不会阻止.具有正超时的延迟意味着close()阻塞直到超时.延迟零暂停导致RST,这就是问题所在. (5认同)
  • 是的,你是对的。我将调整答案以更正我的术语。 (2认同)

小智 6

是否可以安全地删除代码中的延迟取决于应用程序的类型:是“客户端”(打开TCP连接并首先主动关闭它)还是“服务器”(侦听TCP打开和关闭)?在另一方发起关闭后将其关闭)?

如果您的应用程序具有“客户端”的风格(首先关闭),并且您启动和关闭了与不同服务器的大量连接(例如,当您的应用程序是监视应用程序,监督大量不同服务器的可达性时)有一个问题,您的所有客户端连接都停留在TIME_WAIT状态。然后,我建议将超时时间缩短为小于默认值的值,以便仍然正常关闭,但更早地释放客户端连接资源。我不会将超时设置为0,因为0不会通过FIN正常关闭,但会通过RST中止。

如果您的应用程序具有“客户端”的风格,并且必须从同一服务器中获取大量小文件,则不应为每个文件启动新的TCP连接,而在TIME_WAIT中最终导致大量客户端连接,但是保持连接打开并通过同一连接获取所有数据。流浪者选项可以并且应该删除。

如果您的应用程序是一个“服务器”(对同伴的关闭响应为第二关闭),则在close()上,您的连接将正常关闭,并且由于不进入TIME_WAIT状态,因此资源被释放。不应使用流连忘返。但是,如果您的服务器应用程序具有检测长时间闲置(定义为“ long”)的非活动打开连接的管理过程,则可以从您的侧面关闭该非活动连接-将其视为一种错误处理-异常中止关闭。这是通过将linger超时设置为0来完成的。close()然后将向客户端发送RST,告诉他您很生气:-)