TCP连接断连(断网)处理原理
背景:
理想状态下,一个TCP连接可以被长期保持,但是实际情况下,一个看似正常的TCP连接,可能已经断连。两个主机之间通讯,往往需要通过多个中间节点,如:路由器、防火墙等。因此两个主机TCP连接保持同样受中间环节影响。断连的TCP连接已经没有意义了,但是维护这样的连接,可能会浪费服务器的系统资源(尤其是内存和socket资源,客户端一般还好),因此需要服务器采取相应的探测措施。
方案:
TCP探测的原理很简单:定期向连接的远程节点发送一定格式的信息,并等待远程节点的反馈。如果规定时间内返回,那么连接正常,否则已经断连。
常见的三种探测方式是:应用程序自己探测;第三方应用程序探测;TCP协议层的保活探测;
应用程序自己探测:可以根据应用程序的特点选择相应的探测机制和功能实现,比较灵活,但是在实际中,大部分程序并未附带;
第三方应用程序探测:在服务器上安装相应的第三方程序,对所有的TCP连接探测,确保连接是否正常。最大的不足是:所有探测的客户端都需要能够识别来自探测应用的数据报文,因此实际应用中比较少见;
TCP协议层的保活探测:最常用的方案。采用了TCP协议层的保活探测功能,即TCP连接保活定时器,尽管该功能不是RFC规范的一部分,但是几乎所有的Unix系统都支持。
TCP保活机制剖析:
常见系统命令:
保活定时器
AIX (单位是0.5秒)
# no -a | grep keep
tcp_keepcnt = 8
tcp_keepidle = 14400
tcp_keepintvl = 150
Linux (单位是秒)
# sysctl -A | grep keep
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
AIX中的参数解析:
tcp_keepcnt:关闭一个非活跃的连接之前,探测的最大次数;
tcp_keepidle:对一个连接进行有效探测之前运行的最大非活跃时间间隔,默认是2小时;
tcp_keepintvl:2个探测的时间间隔,默认是75秒;
防火墙导致断连分析:
所有服务器主机均划为一个局域网,并处于防火墙 B 之后。由于工作需要,来自工作区局域网的主机 testClient 需和服务器局域网内的 testServer 上的数据库使用 TCP/IP 建立一个连接,testClient 上的上层应用将通过该连接对 testServer 上的数据库进行相应操作。
在实际测试中,我们发现,在 testClient 和 testServer 均工作正常的情况下,testClient 上的客户端在事先没有收到任何异常信息的情况下,所持有的连接会出现非预期的断连现象(在试图通过连接进行数据库操作时,会被告知 connection is reset by foreign host 的错误)。
由于该现象不断出现,并且网络内的中间节点(路由器和交换机等)均工作正常,因此可以排除物理因素(如掉电、宕机等)的可能。为了便于分析断连原因,我们首先查看了 testServer 机器上的默认保活设置:
# no -a | grep keep
tcp_keepcnt = 8
tcp_keepidle = 14400
tcp_keepintvl = 150
testServer 上的 tcp_keepidle 为 14400,即 2 个小时。既然中间节点工作正常,为什么保活机制没有其作用呢?为了进行分析,我们采用 tcpdump 工具捕获 testClient 和 testServer 上的报文信息,见清单 5 和清单 6 所示。
清单 5. 服务器端的 tcpdump 数据输出
1 10:18:58.881950 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: S 1182666808:1182666808(0) ...
2 10:18:58.882001 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: S 3333341833:3333341833(0) ack 1182666809 ...
3 10:18:58.882845 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1 ...
4 ...
5 10:19:03.165568 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: P 1010:1032(22) ack 87 ...
6 10:19:03.166457 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1032 ...
7 12:19:05.445336 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: . 1031:1032(1) ack 86 ...
8 12:19:05.445464 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: R 86:87(1) ack 1031 ...
清单 6. 客户端的 tcpdump 数据输出
1 # tcpdump -e -i eth0 host testServer.cn.ibm.com
2 10:18:55.800553 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: S 1182666808:1182666808(0) ...
3 10:18:55.801778 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: S 3333341833:3333341833(0) ack 1182666809 ...
4 10:18:55.801799 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1 ...
5 ...
6 10:19:00.084662 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: P 1010:1032(22) ack 87 ...
7 10:19:00.084678 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1032 ...
从清单 5 中可以看出,在该连接处于非活跃状态的时间达到 tcp_keepidle 设定的 2 小时时,服务器主机发出了第一个连接保活的探测报文(清单 5 中的第 7 行)。紧接着,服务器主机就收到了来自 testClient 的连接复位报文(清单 5 中的第 8 行)。之后,服务器便关闭了该连接(可以通过 netstat –ni 来查看)。然而,从清单 6 的 tcpdump 数据可以看出, testClient 端并未发送任何报文。那么,是谁向 testServer 发送了复位报文呢?
为了查看上述复位报文的发送者,同样采用上述 tcpdump 命令再次捕获服务器端和防火墙 B 的报文信息(注意:通常需要捕获防火墙主机上网络数据的出口网卡和入口网卡数据),结果显示,防火墙 B 在收到来自 testServer 的第一个探测报文之后就立刻向 testServer 发送了一个复位报文。
上述分析说明,在连接传递完最后一个交互数据之后到服务器端发送第一个保活探测之间,该连接已经被防火墙 B 终止;在此之后,基于该连接的任何报文传递在试图穿过防火墙的时候均会被防火墙丢弃并发送复位报文。
解决方案:
思路:使防火墙和服务器的TCP连接最大非活跃时间一致;
方案1、演长防火墙的TCP连接最大非活跃,使其时间大于服务器的2小时。缺点是:可能会降低防火墙的性能,尤其是维持大量的长时间处于非活跃的连接情况下。
方案2、缩短服务器的TCP连接报活时间。缺点:网络中的报文会增加(TCP报活报文增加)。