为了理解 TCP keepalive (我们之后简称 keepalive) 的作用,你只需要理解它的名字:保持 TCP 连接存活。 这意味着你能够检查已连接的套接字(也称为 TCP 套接字),并确定连接是否仍然正常运行,或者是否已经断开。
Keepalive 的概念非常简单:当你建立一个 TCP 连接时,你会关联一组计时器。 其中一些计时器处理 keepalive 过程。 当 keepalive 计时器归零时,你会向你的对等方发送一个 keepalive 探测包,其中不包含数据,并且 ACK 标志已开启。 你可以这样做,因为 TCP/IP 规范允许这样做,作为一种重复的 ACK,并且远程端点不会有异议,因为 TCP 是面向流的协议。 另一方面,你会收到来自远程主机的回复(它根本不需要支持 keepalive,只需要支持 TCP/IP),其中不包含数据,并且 ACK 也已设置。
如果你收到对 keepalive 探测的回复,你可以断言连接仍然正常运行,而无需担心用户级实现。 实际上,TCP 允许你处理数据流,而不是数据包,因此零长度数据包对于用户程序来说没有危险。
这个过程很有用,因为如果其他对等方失去了连接(例如通过重启),你将会注意到连接已断开,即使你没有流量在其上。 如果 keepalive 探测没有得到你的对等方的回复,你可以断言连接不能被认为是有效的,然后采取正确的行动。
你可以非常愉快地在没有 keepalive 的情况下生活,所以如果你正在阅读本文,你可能正在尝试了解 keepalive 是否是你问题的可能解决方案。 要么就是你真的没有什么更有趣的事情可做,那也没关系。 :)
Keepalive 是非侵入性的,在大多数情况下,如果你有疑问,你可以开启它而不会有做错事的风险。 但是请记住,它会产生额外的网络流量,这可能会对路由器和防火墙产生影响。
简而言之,动动脑筋,小心行事。
在下一节中,我们将区分 keepalive 的两个目标任务
检查失效的对等方
防止由于网络不活动而断开连接
Keepalive 可以用来在你对等方死亡但无法通知你时提醒你。 这可能由于多种原因发生,例如内核崩溃或处理该对等方的进程被强制终止。 另一个说明何时需要 keepalive 来检测对等方死亡的场景是,当对等方仍然存活,但它与你之间的网络通道已断开。 在这种情况下,如果网络没有再次恢复运行,你就相当于遇到了对等方死亡。 这是正常 TCP 操作无法用于检查连接状态的情况之一。
想象一下对等方 A 和对等方 B 之间的简单 TCP 连接:首先是三次握手,从 A 到 B 的一个 SYN 段,从 B 到 A 的 SYN/ACK 返回,以及从 A 到 B 的最终 ACK。 此时,我们处于稳定状态:连接已建立,现在我们通常会等待某人通过通道发送数据。 问题来了:拔掉 B 的电源,它会立即关机,而不会通过网络发送任何信息来通知 A 连接即将断开。 A 从它的角度来看,准备接收数据,并且不知道 B 已经崩溃。 现在恢复 B 的电源并等待系统重启。 A 和 B 现在又恢复了,但是虽然 A 知道与 B 仍然有活动连接,但 B 一无所知。 当 A 尝试通过死连接向 B 发送数据时,情况会自行解决,B 会回复一个 RST 数据包,导致 A 最终关闭连接。
Keepalive 可以告诉你何时另一个对等方变得不可达,而不会有误报的风险。 实际上,如果问题出在两个对等方之间的网络中,keepalive 的操作是等待一段时间然后重试,在将连接标记为断开之前发送 keepalive 数据包。
_____ _____ | | | | | A | | B | |_____| |_____| ^ ^ |--->--->--->-------------- SYN -------------->--->--->---| |---<---<---<------------ SYN/ACK ------------<---<---<---| |--->--->--->-------------- ACK -------------->--->--->---| | | | system crash ---> X | | system restart ---> ^ | | |--->--->--->-------------- PSH -------------->--->--->---| |---<---<---<-------------- RST --------------<---<---<---| | | |
keepalive 的另一个有用的目标是防止由于不活动而断开通道连接。 这是一个非常常见的问题,当你在 NAT 代理或防火墙后面时,会被无缘无故地断开连接。 这种行为是由代理和防火墙中实现的连接跟踪程序引起的,这些程序跟踪所有通过它们的连接。 由于这些机器的物理限制,它们只能在内存中保留有限数量的连接。 最常见和符合逻辑的策略是保留最新的连接,并首先丢弃旧的和不活动的连接。
回到对等方 A 和 B,重新连接它们。 一旦通道打开,等待事件发生,然后将此事件传达给另一个对等方。 如果事件在很长一段时间后才发生怎么办? 我们的连接有其作用域,但代理是不知道的。 因此,当我们最终发送数据时,代理无法正确处理它,连接就会断开。
因为通常的实现方式是在连接收到数据包时将其置于列表顶部,并在需要移除条目时选择队列中最后的连接,所以定期在网络上发送数据包是保持连接活跃,降低被移除风险的有效方法。
_____ _____ _____ | | | | | | | A | | NAT | | B | |_____| |_____| |_____| ^ ^ ^ |--->--->--->---|----------- SYN ------------->--->--->---| |---<---<---<---|--------- SYN/ACK -----------<---<---<---| |--->--->--->---|----------- ACK ------------->--->--->---| | | | | | <--- connection deleted from table | | | | |--->- PSH ->---| <--- invalid connection | | | | |