【问题标题】:why many libraries does not detect dead TCP connections?为什么许多库没有检测到死 TCP 连接?
【发布时间】:2017-02-01 11:41:50
【问题描述】:

TCP 有一个 keep-alive 机制来检测死连接,但令我惊讶的是这个选项在默认情况下是关闭的,而且许多库/工具不使用这个特性。

如果我理解正确,如果来自对等方的所有 FIN/RST 数据包都已丢失,则在 recv 调用中阻塞的 TCP 连接将无法检测连接是否已被对等方实际中止。

客户端的超时参数可能会缓解此问题,但许多库也没有设置超时的选项。一个例子是 mysql-python 连接器没有 recv 超时选项。另一个例子是 Nginx 服务器使用 proxy_pass 与 gunicorn 后端通信,gunicorn worker 可能会由于其上的死连接而停止响应,但 gunicorn worker 无法检测到它。

如果我错了,谁能解释原因或纠正我?

【问题讨论】:

  • 你的问题是建立在一个谬误之上的。不使用 TCP keepalive 与不检测死连接不同。没有必要使用 TCP keepalive 来检测死连接。还有其他方法。例如,检测“连接重置”是绝对可靠的,而且我从未遇到过没有这样做的库。如果您可以生成“许多”不提供 keepalive 或读取超时的库示例,请这样做。
  • 您能否解释一下为什么检测“连接重置”是可靠的?如果来自对等方的 RST 数据包都丢失了怎么办?在没有使用 tcp keepalive 的情况下,recv 操作中的客户端如何知道这种情况?
  • 来自对等方的 RST 不是连接重置的唯一原因。如果足够多的发送和重试未得到确认,本地主机将自行重置连接。如果客户除了recv() 什么都不做,我已经建议了读取超时,
  • timeout 不适用于具有持久连接的服务器端,反向 Web 代理使用到 fastcgi 或某些 wsgi 服务器的持久连接是很常见的。 Fastcgi/wsgi 服务器不断接收来自对等方的请求,读取超时没有意义。

标签: sockets tcp


【解决方案1】:

术语“死连接”有点含糊——它可能意味着以下任何一种:

  1. 对等程序关闭其套接字(或对等程序退出或崩溃,对等计算机的操作系统作为其标准进程清理的一部分关闭了套接字)

  2. 与对等计算机的连接突然断开(这可能是因为对等计算机断电,或者有人拔出了将对等计算机连接到路由器的以太网线,或者对等计算机的 ISP 有路由器失败,或者您的 ISP 出现路由器故障等)

  3. 对等程序仍在运行,但只是决定(出于某种原因,可能是由于错误)不再在其 TCP 套接字上调用 recv()。

  4. 您的程序和远程对等方之间的数据包路径仍然存在,有点,但是沿着该路径的东西正在丢弃如此多的数据包,以至于 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 选项的设计者希望默认节省带宽,并且发送自动保活数据包会占用额外的带宽,因此他们决定默认禁用保活选项。此外,按照现代标准(例如小时),默认的 send-a-keepalive 间隔通常很长,并且在某些操作系统上很难更改,除非在系统范围内进行更改,这使得 SO_KEEPALIVE 对许多应用程序的用处有限。

对于条件 (3) 和 (4),TCP 连接并没有真正“死”,只是某些设备(对等程序,或者您的程序和对等方之间的某个网络设备)是不合作。由于 TCP 层无法知道正在使用它的应用程序试图实现什么,因此它明智地不会尝试在这方面事后猜测它们,并且除非您明确告诉它关闭,否则它会使 TCP 连接保持打开状态( ) 连接。

既然我们已经描述了 TCP 层的行为,那么使用它的应用程序和 API 呢?即他们为什么不尝试通过提供更好的检测来改进基本的 TCP 堆栈行为?答案是他们中的一些人这样做;例如通过周期性地在任何本来空闲的套接字上发送虚拟“ping”消息,只是为了“刺激”TCP堆栈检测何时没有ACK返回,如上面关于条件(2)的段落中所述。有些人更进一步,期望远程对等方发送相应的“pong”消息以在(这么多)秒内返回同一个套接字,如果没有,程序将单方面关闭套接字。这种方法可行,但它也会对您的网络性能做出假设,当对等方通过缓慢或不可靠的网络连接时,这可能会导致误报,从而导致不必要的断开连接,这就是许多应用程序/库不这样做的原因t 实现这个(或者至少默认不启用它)。

【讨论】:

  • 你需要改变。从“客户”到“同行”。
  • '带宽很宝贵' --> 这在过去可能是真的,但现在带宽越来越大,而且光纤越来越便宜,我认为至少大多数网络相关的库(尤其是服务器端)库)应该考虑为应用程序程序员提供一个接口来打开keepalive选项
【解决方案2】:

keep-alive 默认关闭对我来说并不奇怪。

因为对等程序总是有可能由于错误或错误等而冻结。在这种情况下,recv 也会永远阻塞,即使 TCP 连接是活动的。所以keep-alive毕竟可能没那么有用(除了防止路由器断开连接)。各种原因可能会导致您的recv 永远被阻止。

此外,通用的低级底层协议应该尽可能简单。


此外,我对您关于无法设置超时的示例也不感到惊讶。看看这个世界上最流行的软件工具。它们经过打磨、进化、优化和使用了这么长时间。然而,他们中的许多人仍然经常冻结、崩溃或行为不端。编写正确的代码是一项细致的工作。更不用说进一步的要求,如安全性、跨平台、向后兼容性。程序员的生活并不轻松。

【讨论】:

  • 是的,tcp keepalive 不能防止应用程序的错误,但那是另一回事,如果连接问题可以被 tcp 堆栈自动检测到,这样程序员就可以集中应用程序逻辑。这并不妨碍它的用处。
  • 这不是第一次在一组条件下做出决定,后来证明这不是后来条件下的最佳决定,但到那时再改变已经太迟了默认,因为它会破坏向后兼容性:)
  • 我认为打开 tcp keepalive 不会破坏任何兼容性,只是增加了一点带宽。
  • @user869210 我不确定。在最早的 1974 版本的 TCP 规范中,我找不到保持活动状态。 1989 年的 RFC1122 包括它。不过在 2017 年,我猜你不需要关心旧规格。
猜你喜欢
  • 1970-01-01
  • 2016-01-24
  • 1970-01-01
  • 2011-02-09
  • 2019-01-28
  • 2014-06-28
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
相关资源
最近更新 更多