如果A机器与B机器网络connect成功后从未互发过数据,此时其中一机器突然断电,则另外一台机器与断电的机器之间的网络连接处于哪种状态?
笔者实测如下:
虚拟机A:
CentOS 7,192.168.133.131,TCP Server
虚拟机B:
CentOS 7,192.168.133.128,TCP Client
1、建立连接时:
A机器
[root@localhost ~]# netstat -nalp|grep 1883
tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 48261/epltest
tcp 0 0 192.168.133.131:1883 192.168.133.128:39170 ESTABLISHED 48261/epltest
B机器
[root@localhost firecat]# netstat -nalp|grep 1883
tcp 0 0 192.168.133.128:39170 192.168.133.131:1883 ESTABLISHED 7470/./eplClient
2、B机器断电时,A机器查看状态如下:
[root@localhost ~]# netstat -nalp|grep 1883
tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 48261/epltest
tcp 0 0 192.168.133.131:1883 192.168.133.128:39170 ESTABLISHED 48261/epltest
结论:断电后,继续保持ESTABLISHED的网络状态。
3、笔者继续实测,让A断电,B保持,结论还是一样,ESTABLISHED。
4、如果A不变,B机器kill client进程,则B机器会出现TIME_WAIT的网络状态。
[root@localhost firecat]# kill -9 7459
[root@localhost firecat]# netstat -nalp|grep 1883
tcp 0 0 192.168.133.128:60114 192.168.133.131:1883 TIME_WAIT -
5、我们如果手动拔掉网线,在没有心跳机制的前提下,现象也是一样的。
6、具体原因如下:
什么是半开连接?
当客户端与服务器建立起正常的TCP连接后,如果客户主机掉线(网线断开)、电源掉电、或系统崩溃,服务器进程将永远不会知道(通过我们常用的select,epoll监测不到断开或错误事件),如果不主动处理或重启系统的话对于服务端来说会一直维持着这个连接,任凭服务端进程如何望穿秋水,也永远再等不到客户端的任何回应。这种情况就是半开连接,浪费了服务器端可用的文件描述符。
如何处理?
熟悉套接字通用选项的朋友一定已经有了想法。TCP套接字不是有个保持存活选项SO_KEEPALIVE嘛,如果在两个小时之内在该套接字的任何一个方向上都没数据交换,TCP就自动给对端发送一个保持存活探测分节,如果此TCP探测分节的响应为RST,说明对端已经崩溃且已经重新启动,该套接字的待处理错误被置为ECONNRESET,套接字本身则被关闭。如果没有对此TCP探测分节的任何响应,该套接字的处理错误就被置为ETIMEOUT,套接字本身则被关闭。
确实,这个选项确实可以处理我们前面遇到的TCP半开连接的问题,但是默认两小时间隔探测的实时性是不是差了些呢?当然,我们可以通过修改内核参数改小时间间隔,完美了吧?但是必须注意的是大多数内核是基于整个内核维护这些时间参数的,而不是基于每个套接字维护的,因此如果把无活动周期从两小时改为(比如)2分钟,那将影响到该主机上所有开启了此选项的套接字。我想大家都不会愿意承担服务器端的这种不确定性吧。另外,心跳除了说明应用程序还活着(进程存在,网络畅通),更重要的是表明应用程序能正常工作。而SO_KEEPALIVE由操作系统负责探查,即便是进程死锁或有其他异常,操作系统也会正常收发TCP keepalive消息,而对方无法得知这一异常。
没关系,其实我们可以在应用层模拟SO_KEEPALIVE的方式,用心跳包来模拟保活探测分节。由于服务器通常要承担成千上万的并发连接,所以肯定是由客户端在应用层进行心跳来模拟保活探测分节,客户端多次收不到服务器的响应时可终止此TCP连接,而服务端可监测客户端的心跳包,若在一定时间间隔内未收到任何来自客户端的心跳包则可以终止此TCP连接,这样就有效避免了TCP半开连接的情况。