TCP 机制引发设备状态异常解读
1. 问题背景
最近碰到一个有趣的问题:
为测试极端情况下程序的健壮性,把 TCP 通信的设备拔掉网线/断开电源后,连接程序并不能马上报警提示设备已断开,而是要过一段时间(经统计,大概为 15 分钟)才能断开,测试的系统是 Linux,发行版为 CentOS 7.9。
更有意思的是,为了验证问题我在本地 Windows 下进行调试,发现基本上 10 多秒就能反馈设备断开,而不是反馈的 15 分钟。
另外,为什么断开设备后不会马上异常,无论 Windows 还是 Linux 都能正常运行一段时间才提示设备断开。按正常的理解:
对端网线被拔掉后物理层就会断开,那么基于物理层的传输层也会断开,所以原本的 TCP 连接也不会存在了。就像我们打电话时,如果其中一方网络信号中断,那么本次通话就彻底断了。
2. 实践测试
从表现来看,并非像理论上理解那样:对端网线被拔掉后物理层就会断开,那么基于物理层的传输层也会断开,所以原本的 TCP 连接也不会存在了。
事实上拔掉对端网线并不影响传输层(TCP)通信,TCP 是操作系统实现的逻辑概念,与拔掉对端网线的物理动作并没有任何关联,对端由于物理层被切断也无法向你发送中断信号,因此也不会对现有 TCP 产生任何影响。
为此,我在服务器上专门做了测试,首先正常启动客户端程序,提示设备连接成功并输出相应信息,此时查看服务器 TCP 连接情况如下:
拔掉设备网线,再查看服务器 TCP 连接仍然保持连接:
通过上面测试可以验证:拔掉对端网线并不会影响 TCP 连接状态,甚至拔掉本机网线也不会影响 TCP 连接状态。
3. 原理分析
客户端调用 Sokect 向设备端发送报文时,并不是真的发送,而是将待发送内容写入系统 TCP 带发送缓存区域中,因此并不会直接报错。由于 TCP 协议具有可靠性设计,因此当设备端拔掉网线,客户端向设备发送的数据报文得不到任何响应时,在超过等待时长后,会触发 TCP 协议的超时重传机制,只不过此时重传也不能解决问题。
3.1. 网线重插
如果在重传报文的过程中,恰好把网线插回去了,由于拔掉网线并不会改变客户端的 TCP 连接状态,并且还是处于 ESTABLISHED 状态,所以这时客户端是可以正常接收服务端发来的数据报文的,然后客户端就会回 ACK 响应报文。
此时,客户端和设备端的 TCP 连接将依然存在且工作状态不会受到影响,给应用层的感觉就像什么事情都没有发生,这就是 TCP 连接的可靠性设计。
3.2. 不插网线
如果在客户端 TCP 协议重传报文的过程中,设备端一直没有将网线插回去,那么客户端超时重传报文的次数达到一定阈值后,系统内核就会判定该 TCP 有问题。然后就会通过 Socket 接口告诉应用程序该 TCP 连接出问题了,于是客户端的 TCP 连接就会断开。
3.3. 超时时间是多少
TCP 协议当数据发送失败时,会尝试重发,具体重发次数可以设定,如果重发不成功,会间隔一定时间再尝试,这个间隔时间被称作 RTO (Retransmission Timeouts), 一般情况下超时时间可以参考:
注意 RTO 时间是指数上升的,具体与操作系统内核实现有关,上图是一般情况的结果,可以发现重试 15 次后,第 16 次不成功的大概时间是 15 分钟,与问题反馈的情况一致。
如果想修改这个值,Linux 和 Windows 具体方法如下:
3.3.1. Linux
在 Linux 系统中,提提供了一个叫 tcp_retries2 配置项,默认值是 15
1 | 可用此命令查看 |
3.3.2. Windows
Windows 系统中 TCP/IP 相关参数配置在下面注册表键中
1 | HKEY_LOCAL_MACHINE |
详情可查看这里
4. 最后补充
需要注意的是上面讨论的是:设备断开网线后,有数据传输的场景
。那么没有数据传输时会如何处理?实际上 TCP 协议在设计时设计了 KeepAlive 机制。
如果没有开启 TCP KeepAlive 机制:
在客户端拔掉网线后,并且双方都没有进行数据传输,那么客户端和服务端的 TCP 连接将会一直保持存在。
如果开启了 TCP KeepAlive 机制:
在客户端拔掉网线后,即使双方都没有进行数据传输,在持续一段时间后,TCP 就会发送 KeepAlive 探测报文。
根据 KeepAlive 探测报文响应情况,会有以下两种可能:
- 1) 如果对端正常工作:当探测报文被对端收到并正常响应, TCP 保活时间将被重置,等待下一个 TCP 保活时间的到来;
- 2)如果对端主机崩溃或对端由于其他原因导致报文不可达:当探测报文发送给对端后没有响应,连续几次,达到保活探测次数后,TCP 会报告该连接已经死亡。
所以,TCP 保活机制可以在双方没有数据交互的情况,通过 TCP KeepAlive 机制的探测报文,来确定对方的 TCP 连接是否存活。