及时释放服务端与客户端之间的TCP连接的方法
TCP的状态转换图
先贴上tcp状态转换图,方便后面分析问题
感知对端关闭,及时关闭己方连接
前几天遇到了一个问题,服务端下线,主动断开了连接。但客户端并没有感知到,而是继续使用该连接,导致下次调用服务端报错。
查看客户端和服务端的tcp状态,服务端处于FIN_WAIT_2状态,而客户端处于CLOSE_WAIT状态。
结合上面四次挥手的图,分析原因。处于FIN_WAIT_2状态的服务端主动断开了连接,即在代码里调用了close()函数。而另一端响应主动方的断开请求,被动断开(这一过程是操作系统自动完成的),被动断开之后处于CLOSE_WAIT。之后再无其他动作,服务端认为断开了连接,而服务端仍然认为连接可用。
如何避免这个问题呢,就是在服务端调用close之后,客户端感知到这个动作,然后向服务端发送一个FIN包,即调用close()方法。
那么客户端可以感知到服务端的close动作吗?
Java中我们使用Socket对象完成tcp的相关操作,其本身存在一些isConneted等函数判断连接的状态,但这些都是本地状态,无法感知到另一端的状态。有其他办法吗?一片很好的帖子给出了答案 https://stackoverflow.com/questions/10240694/java-socket-api-how-to-tell-if-a-connection-has-been-closed
其中提到了一条:
7. As a result of some experiments with Java 7 on Windows XP it also appears that if:
* you're selecting on OP_READ
select() returns a value of greater than zero
the associated SelectionKey is already invalid (key.isValid() == false)
it means the peer has reset the connection. However this may be peculiar to either the JRE version or platform.
意思是在WindowsXP下,如果客户端主动关闭了连接服务端的select会停止阻塞,返回的SelectionKey判断isValid()会返回false。我本机是Win7系统,测试结果不太一样。Win7仍然返回的SelectionKey仍然是valid的,但是socketChannel.read(buffer)会返回-1,表明连接以及不可用。在Linux机器上的结果和本地Win7是一样的。
另外,JDK官方的解释也没有说valid代表了远端是否关闭了连接:
A selection key is created each time a channel is registered with a selector. A key remains valid until it is cancelled by invoking its cancel method, by closing its channel, or by closing its selector. Cancelling a key does not immediately remove it from its selector; it is instead added to the selector's cancelled-key set for removal during the next selection operation. The validity of a key may be tested by invoking its isValid method.
主动关闭后,及时回收无效连接
由于服务端需要处理大量连接,所以TCP是一个宝贵的资源,需要及时的回收。对于不可用的连接需要尽快的释放掉或者重用。
某些情况下,服务端由于压力等原因需要断开某些连接。我们知道TCP连接断开需要四次回收,主动调用close方法并且客户端ack后只完成了两次挥手,此时的连接并没有完全关闭。那么,如果客户端一直不主动发送FIN包关闭,服务端就要一直保持连接吗?当然不是,系统会回收这样的连接,有一些参数控制了连接状态超时时间。有一篇文章总结了TCP协议中的超时机制http://blog.qiusuo.im/blog/2014/03/19/tcp-timeout/。
主动关闭后本地状态将进入FIN_WAIT_2 ,而FIN_WAIT_2 Timer就会及时回收掉本地端主动关闭远端却长时间不主动关闭的连接。其配置参数名字为tcp_fin_timeout 。