什么时候需要 TCP 选项 SO_LINGER (0)?

Posted

技术标签:

【中文标题】什么时候需要 TCP 选项 SO_LINGER (0)?【英文标题】:When is TCP option SO_LINGER (0) required? 【发布时间】:2011-04-15 00:16:22 【问题描述】:

我想我理解该选项的正式含义。在我现在处理的一些遗留代码中,使用了该选项。客户抱怨 RST 是从其一侧对 FIN 的响应,因为其一侧的连接关闭。

我不确定我是否可以安全地移除它,因为我不知道什么时候应该使用它。

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

【问题讨论】:

你应该删除它。它不应该在生产代码中使用。我唯一一次看到它被使用是因为基准测试无效。 【参考方案1】:

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

在我们开始这个小讲座之前:

正常的 TCP 终止 TIME_WAIT FINACKRST

正常的 TCP 终止

正常的 TCP 终止序列如下所示(简化):

我们有两个同伴:A 和 B

    A 呼叫close()
A 向 B 发送 FIN A 进入FIN_WAIT_1 状态
    B 收到FIN
B 向 A 发送ACK B 进入CLOSE_WAIT 状态
    A 收到ACK
A 进入FIN_WAIT_2 状态
    B 呼叫close()
B 向 A 发送FIN B 进入LAST_ACK 状态
    A 收到FIN
A 向 B 发送 ACK A 进入TIME_WAIT 状态
    B 收到ACK
B 进入CLOSED 状态——即从套接字表中删除

TIME_WAIT

因此发起终止的对等方——即首先调用close()——将最终处于TIME_WAIT状态。

要了解为什么TIME_WAIT 状态是我们的朋友,请阅读 Stevens 等人的“UNIX 网络编程”第三版(第 43 页)中的第 2.7 节。

但是,如果服务器上有大量处于TIME_WAIT 状态的套接字,则可能会出现问题,因为它最终可能会阻止新连接被接受。

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

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

如果服务端需要关闭连接,设计应用协议让服务端要求客户端调用close()

何时使用超时 0 的 SO_LINGER

再次,根据“UNIX 网络编程”第三版第 202-203 页,在调用 close() 之前将 SO_LINGER 设置为超时 0 将导致启动正常的终止序列

相反,设置此选项并调用close() 的对等方将发送RST(连接重置),指示错误情况,这就是另一端的感知方式。您通常会看到“对等连接重置”之类的错误。

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

但是,在某些情况下还是需要这样做:

如果您的服务器应用程序的客户端行为不端(超时、返回无效数据等),则中止关闭可以避免陷入CLOSE_WAIT 或最终进入TIME_WAIT 状态。 如果您必须重新启动当前有数千个客户端连接的服务器应用程序,您可以考虑设置此套接字选项以避免TIME_WAIT(从服务器端调用close() 时)中的数千个服务器套接字,因为这可能会阻止服务器在重新启动后无法为新的客户端连接获取可用端口。 在上述书籍的第 202 页上,它特别指出:“在某些情况下,需要使用此功能发送异常关闭。一个示例是 RS-232 终端服务器,它可能会在 CLOSE_WAIT 试图将数据传送到卡住的终端端口,但如果收到RST 以丢弃待处理的数据,则会正确重置卡住的端口。”

我会推荐this长篇文章,我相信它可以很好地回答你的问题。

【讨论】:

TIME_WAIT 只有在没有开始引起问题时才成为朋友:***.com/questions/1803566/… 那么如果你正在编写一个 Web 服务器呢?你如何“告诉客户启动关闭”? @ShaunNeal 你显然不知道。但是编写良好的客户端/浏览器将启动关闭。如果客户端表现不佳,幸运的是我们有 TIME_WAIT 暗杀以确保我们不会用完套接字描述符和临时端口。【参考方案2】:

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

当 TCP 连接完全关闭时,发起关闭的一端(“主动关闭”)以连接在 TIME_WAIT 中停留几分钟而告终。因此,如果您的协议是 服务器 发起关闭连接的协议,并且涉及大量短期连接,那么它可能容易受到此问题的影响。

不过,这不是一个好主意 - TIME_WAIT 的存在是有原因的(以确保来自旧连接的杂散数据包不会干扰新连接)。如果可能,最好将您的协议重新设计为客户端发起关闭连接的协议。

【讨论】:

我完全同意。我见过一个监控应用程序,它启动了很多(每 X 秒有几千个短时连接),并且它有更大的规模(一千个连接)。我不知道为什么,但应用程序没有响应。有人建议 SO_LINGER = true, TIME_WAIT = 0 来快速释放操作系统资源,经过短暂调查,我们确实尝试了这个解决方案,结果非常好。 TIME_WAIT 不再是这个应用程序的问题。 我不同意。位于 TCP 之上的应用程序级协议应设计为客户端始终启动连接关闭。这样,TIME_WAIT 将坐在客户端,不会造成任何伤害。请记住,正如“UNIX 网络编程”第三版(Stevens 等人)第 203 页所说:“TIME_WAIT 状态是您的朋友,可以帮助我们。与其试图避免该状态,我们应该理解它(第 2.7 节) 。” 如果客户端想要每 30 秒打开 4000 个连接怎么办(这个监控应用程序是客户端!因为它启动连接)?是的,我们可以重新设计应用程序,在基础设施中添加一些本地代理,更改要推送的模型。但是,如果我们已经有这样的应用程序并且它还在增长,那么我们可以通过调整 twe linger 来使其工作。您更改了一个参数,然后您突然有了工作应用程序,而无需投入预算来实施新架构。 @bartosz.r:我只是说使用超时 0 的 SO_LINGER 确实应该是最后的手段。同样,在“UNIX 网络编程”第三版(Stevens 等人)第 203 页中,它还说您有数据损坏的风险。考虑阅读 RFC 1337,您可以在其中了解为什么 TIME_WAIT 是您的朋友。 @caf 不,经典的解决方案是连接池,就像在每个重型 TCP API 中看到的那样,例如 HTTP 1.1。【参考方案3】:

当 linger 开启但超时为零时,TCP 堆栈不会在关闭连接之前等待发送待处理的数据。数据可能因此而丢失,但是通过以这种方式设置 linger ,您将接受这一点并要求立即重置连接,而不是正常关闭连接。这会导致发送 RST 而不是通常的 FIN。

感谢 EJP 的评论,详情请参阅 here。

【讨论】:

我明白这一点。当我们想使用硬重置时,我要问的是“现实”示例。 当你想中止连接时;因此,如果您的协议验证失败并且您的客户突然对您说废话,您将中止与 RST 的连接等。 您将零延迟超时与延迟关闭混淆了。延迟关闭意味着 close() 不会阻塞。以正超时逗留意味着 close() 阻塞直到超时。超时时间为零会导致 RST,这就是问题所在。 是的,你是对的。我会调整答案以更正我的术语。【参考方案4】:

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

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

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

如果您的应用程序是“服务器”(作为对对等方关闭的反应而关闭),在 close() 时,您的连接将正常关闭,并且由于您没有进入 TIME_WAIT 状态而释放资源。不应使用灵儿。但是,如果您的服务器应用程序有一个监控进程来检测长时间闲置的非活动打开连接(“长”将被定义),您可以从您的身边关闭这个非活动连接 - 将其视为一种错误处理 - 中止关闭。这是通过将 linger timeout 设置为 0 来完成的。close() 然后会向客户端发送一个 RST,告诉他你很生气 :-)

【讨论】:

【参考方案5】:

在服务器中,当断开行为不端的客户端时,您可能希望发送RST 而不是FIN。这会跳过服务器中的FIN-WAIT,然后是TIME-WAIT 套接字状态,这可以防止耗尽服务器资源,从而防止这种拒绝服务攻击。

【讨论】:

【参考方案6】:

我喜欢 Maxim 的观察,即 DOS 攻击会耗尽服务器资源。在没有真正恶意的对手的情况下也会发生这种情况。

某些服务器必须处理“无意 DOS 攻击”,当客户端应用程序存在连接泄漏错误时会发生这种情况,在这种情况下,它们会不断为发送到服务器的每个新命令创建新连接。然后,如果它们遇到 GC 压力,可能最终会关闭它们的连接,或者连接最终会超时。

另一种情况是“所有客户端都具有相同的 TCP 地址”的情况。然后客户端连接只能通过端口号来区分(如果它们连接到单个服务器)。如果客户端出于任何原因开始快速循环打开/关闭连接,它们可能会耗尽 (client addr+port, server IP+port) 元组空间。

所以我认为最好建议服务器在看到大量处于 TIME_WAIT 状态的套接字时切换到 Linger-Zero 策略 - 尽管它不能修复客户端行为,但可能会减少影响。

【讨论】:

【参考方案7】:

服务器上的监听套接字可以使用时间为 0 的 linger 来访问立即绑定回套接字并重置尚未完成连接的任何客户端。 TIME_WAIT 仅在您拥有多路径网络并且可能以错误排序的数据包结束或以其他方式处理奇怪的网络数据包排序/到达时间时才有意义。

【讨论】:

【参考方案8】:

我刚刚在websockets RFC (RFC 6455) 中看到,它明确指出服务器 应该首先在TCP 套接字上调用close()(!)

我很敬畏,因为事实上我在这个线程中持有@mgd 的答案/帖子,而 RFC 显然反对这一点。但是,也许这种情况下,将逗留时间设置为 0 是可以接受的。

底层 TCP 连接,在大多数正常情况下,应该关闭 首先由服务器,以便它保持 TIME_WAIT 状态而不是 客户

我很想听听对此的任何想法/见解。

【讨论】:

以上是关于什么时候需要 TCP 选项 SO_LINGER (0)?的主要内容,如果未能解决你的问题,请参考以下文章

TCP的选项SO_LINGER设置延时关闭

TCP协议中的SO_LINGER选项

setsockopt 设置TCP的选项SO_LINGER

Linux网络编程socket选项之SO_LINGER,SO_REUSEADDR

为什么SO_LINGER选项没有0超时或10秒超时没有立即删除套接字或10秒后?

SO_LINGER 选项