TCP的半开连接

项目里面要测试平板终端的关机功能,所以需要我服务器接收发送端的关机命令,然后转发至接收端。因为考虑到方便测试,所以平板发过来的命令是‘重启’,不然每一台都要人工去开机很麻烦,重启之后各终端再连接服务器,进入app主界面。很简单的一个逻辑,可是在实际测试的时候,却出现了问题,影响了后面程序的逻辑,后来仔细分析原来是TCP的半开连接所导致!以此记录。

按照以往正常的思维逻辑,我连终端都已经关闭,并且在5s、6s内才重登启登陆,那么之前它和服务器的连接应该早已断掉了吧,服务器也应该立马检测的到呀?即是类似的相当于我都把机子关了、网线拔了,说明物理连接都断了,更别提逻辑上的TCP长连接,再插上网线也只能再建立一个新的连接来继续进行请求。网上的比喻:TCP长连接好比以前我们用的有线电话,甲和乙通话的过程中,倘若其中一人的电话线被拔掉了,连接就彻底断了。即使再插上电话线也不可能自动恢复通话,我们不得不重新拨通。不过后来几经思考、测试,我才反应过来这是TCP的半开连接的的原因。

当客户端与服务器建立起正常的TCP连接后,如果客户主机网线断开、电源掉电、或系统崩溃,服务器进程将永远不会知道(通过我们常用的select,epoll监测不到断开或错误事件),如果不主动处理或重启系统的话对于服务端来说会一直维持着这个连接,任凭服务端进程如何望穿秋水,也永远再等不到客户端的任何回应。这种情况就是半开连接,浪费了服务器端可用的文件描述符。

我想我服务器用的还是socket.io,算是好的,它里面自带了keepAlice,可能默认的时间比较长,如果在这个时间对方没反应,服务器才将该连接主动断开。由此说明,在tcp里面,像我们这种对端关机、拔网线之类的断开,服务器是不能感知到的,除非做心跳包检测。

所以回到我们项目中,当第一次终端关机并重连的时候,服务器还并没有对上次的socket进行清理回收,则第二次登陆上来终端又发起了socket连接,所以在这里以前没有作处理,则导致了后面程序逻辑的错误。还有引申一点就是:如果网络断开的时间很长,超出了SO_KEEPALIVE设定的探测时间间隔,或者两端期间在此有了任何针对此长连接的网络操作。当连上网线时就会出现ETIMEDOUT或者ECONNRESET的错误。你必须重新建立一个新的长连接进行网络操作。所以作为客户端你应该捕获这个异常作相应的程序处理。