首先我们先来回顾一下tcp关闭连接的过程:
假设A和B连接状态为EST,A需要主动关闭:
A发送FIN给B,并将状态更改为FIN_WAIT1,
B接收到FIN将状态更改为CLOSE_WAIT,并回复ACK和FIN
A收到ACK后将状态更改为FIN_WAIT2,收到FIN后,更改状态为WAIT_TIMEOUT并给B返回ACK
B收到ACK后,将关闭自己的链接CLOSE。
问题就在此时,A将处于WAIT_TIMEOUT状态长达2MSL时常(RFC793定义了MSL为2分钟,Linux设置成了30s),为什么会有这个状态存在呢?为什么不直接进入close状态呢?
主要有两个原因:
1、TIME_WAIT确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到Ack,就会触发被动端重发Fin,一来一去正好2个MSL;
2、有足够的时间让这个连接不会跟后面的连接混在一起(如果连接被重用了,那么这些延迟收到的包就有可能会跟新连接混在一起)。
我们经常就会在服务器上发现有很多的wait_timeout,特别常见的就是http服务器,从上面的描述来看,这个状态应该是主动关闭的一方,才会有的状态,我们有没有想过,就是为什么服务器端会主动断开连接呢?不应该是客户端主动断开吗?
据说,其实在最初的http协议,服务器在发送完客户端需要的内容后,就主动关闭连接,因为当时的客户端,需要等待所有效果渲染完毕,才会主动断开连接,为了照顾当时性能低下的服务器,更好的服务其他客户,才在协议中这样规定,说到底就是http协议太老了,所以Google才会推他的SPDY协议。
那么,如何解决这种问题呢?
网上有两种方法,设置tcp_tw_reuse和tcp_tw_recycle,但是这两种方法据说都存在一些危险性:
具体引用coolshell的blog:
- 关于tcp_tw_reuse。官方文档上说tcp_tw_reuse 加上tcp_timestamps(又叫PAWS, for Protection Against Wrapped Sequence Numbers)可以保证协议的角度上的安全,但是你需要tcp_timestamps在两边都被打开(你可以读一下tcp_twsk_unique的源码 )。我个人估计还是有一些场景会有问题。
- 关于tcp_tw_recycle。如果是tcp_tw_recycle被打开了话,会假设对端开启了tcp_timestamps,然后会去比较时间戳,如果时间戳变大了,就可以重用。但是,如果对端是一个NAT网络的话(如:一个公司只用一个IP出公网)或是对端的IP被另一台重用了,这个事就复杂了。建链接的SYN可能就被直接丢掉了(你可能会看到connection time out的错误)(如果你想观摩一下Linux的内核代码,请参看源码 tcp_timewait_state_process)。
既然都这么危险,该怎么解决呢?目前我能想到的就是增加keepalive的时长,等着客户端来断开连接,但是有时候浏览器可能会设置断开的时间更长……
其实,TIME_WAIT表示的是你主动断连接,所以,这就是所谓的“不作死不会死”。试想,如果让对端断连接,那么这个破问题就是对方的了,呵呵。另外,如果你的服务器是于HTTP服务器,那么设置一个HTTP的KeepAlive有多重要(浏览器会重用一个TCP连接来处理多个HTTP请求),然后让客户端去断链接(你要小心,浏览器可能会非常贪婪,他们不到万不得已不会主动断连接)。
不知道大家在服务器上,是怎么解决这个问题的?
引用链接:
http://coolshell.cn/articles/11564.html
https://www.zhihu.com/question/24338653