TCP有一个保持活动的机制来检测死连接,但是我很惊讶默认情况下此选项已关闭,许多库/工具不使用此功能。
如果我理解正确,如果来自对等方的所有FIN / RST数据包都已丢失,则在recv调用中阻塞的TCP连接将无法检测连接是否已被对等方实际中止。
客户端的超时参数可以缓解此问题,但许多库也没有设置超时的选项。一个例子是mysql-python连接器没有recv超时选项。另一个例子是Nginx服务器与一个带有proxy_pass的gunicorn后端进行通信,gunicorn工作者可能会因为死连接而停止响应,但是gunicorn工作者无法检测到它。
如果我错了,有人可以解释原因或纠正我吗?
答案 0 :(得分:3)
术语“死连接”有点含糊不清 - 它可能意味着以下任何一种情况:
对等程序关闭了它的套接字(或者对等程序退出或崩溃,对等计算机的操作系统关闭了套接字,作为其标准进程清理的一部分)
对等计算机的连接突然丢失(这可能是因为对等计算机断电,或有人拔出连接对等计算机到路由器的以太网线,或者对等方的ISP有路由器失败,或者你的ISP有路由器故障,或等等)
对等程序仍在运行,但由于某种原因(可能是由于某个错误)决定停止在他的TCP套接字上再次调用recv()。
您的程序和远程对等方之间的数据包路径仍然存在,但该路径上的某些内容正在丢弃如此多的数据包,以至于TCP连接的有效传输速率已降至大约为零。
所以要回答的第一个问题是,TCP层自身会检测上述哪个条件?
条件(1)很简单 - 对等方的TCP堆栈将向您发送FIN数据包,当程序的网络堆栈收到它们时,它将确定TCP连接已关闭并相应地执行,因此你的recv()调用将很快返回0。
在条件(2)中,答案是“有时” - 特别是,如果你的程序在套接字的输出缓冲区中有任何TCP数据,它试图发送给对等体,它永远不会得到任何ACK数据包关于该数据,然后在一定数量的超时(以及随后的数据包重发尝试)之后,您的计算机的TCP堆栈将放弃,声明连接死机,并单方面关闭TCP连接;此时,recv()将返回0.如果没有尝试发送的传出TCP数据包,则另一方面,本地TCP堆栈将不会等待任何ACK返回,因此它赢了当它没有得到它们时超时,因此它不会放弃并关闭TCP连接。在这种情况下,你的recv()调用很可能无限期地阻塞,因为TCP连接是空闲的,并且TCP堆栈无法知道对等体已经消失(而不是现在只是不发送任何数据)。在这种情况下,SO_KEEPALIVE选项是要处理的,但由于SO_KEEPALIVE选项的设计者希望默认节省带宽,并且发送自动keepalive数据包会占用额外的带宽,因此他们决定默认禁用keepalive选项。此外,默认的send-a-keepalive间隔通常很长,按现代标准(例如几小时)而且在某些操作系统上很难改变,除非在系统范围内,这使SO_KEEPALIVE对许多应用程序的用处有限。
对于条件(3)和(4),TCP连接实际上并不“死”,只是某个设备(对等程序,或者程序和对等体之间的某个网络设备)是不合作。由于TCP层无法知道正在使用它的应用程序正在尝试实现什么,因此明智地不会尝试在这方面进行二次猜测,并且除非您明确告诉它关闭,否则它将保持TCP连接处于打开状态( )连接。
现在我们已经描述了TCP层的行为,那些使用它的应用程序和API呢?即为什么他们不试图通过提供更好的检测来改进基本的TCP堆栈行为?答案是他们中的一些人会这样做;例如通过在任何套接字上周期性地发送虚拟“ping”消息,否则这些消息将是空闲的,简单地“刺激”TCP栈以检测何时没有ACK返回,如上面关于条件(2)的段落中所述。有些甚至更进一步,期望远程对等体在(很多)秒内发送相应的“pong”消息回到同一个套接字上,如果没有,程序将单方面关闭套接字。这类工作,但它也假设你的网络的性能,并且当对等通过慢速或不可靠的网络连接时,这可能导致误报并因此导致不必要的断开,这就是为什么许多应用程序/库不应该实现这一点(或者至少默认情况下不启用它)。
答案 1 :(得分:0)
默认情况下,保持活动关闭对我来说并不奇怪。
因为对等程序总是可能因错误或错误等而冻结。在这种情况下,即使TCP连接处于活动状态,recv
也会永久阻塞。所以keep-alive毕竟不是那么有用(除了防止路由器掉线)。各种原因可能会导致recv
永远阻止。
此外,通用的低级底层协议应该尽可能简单。
此外,我对您无法设置超时的示例并不感到惊讶。看看这个世界上最流行的软件工具。它们经过抛光,演化,优化和使用了很长时间。然而,他们中的许多人仍然经常冻结,崩溃或行为不端。编写正确的代码是一丝不苟的工作。更不用说安全性,跨平台,向后兼容性等其他要求。程序员的生活并不容易。