C++网络编程之十四TIME_WAIT和CLOSE_WAIT

一、网络通信中的一些铺垫说明

在实际的网络开发中,可能遇到各种各样的问题。这些问题可能千奇百怪,但万变不离其宗。有过TCP/IP开发经验的知道,TCP的通信要通过三次握手和四次挥手来实现通信的连接和断开。而如果在这三次握手和四次挥手过程中如果遇到一些异常,会是什么情况呢?比如三次握手时没有实现完整的握手过程;亦或四次挥手时没有形成完整的挥手过程 。这是从通信底层来看问题,但如果从上层应用看呢?如何将底层的一些问题与上层暴露出来的现象对应起来,这才是重点。
今天就讨论一个比较常见的现象,就是在服务端上有很多TIME_WAIT和CLOSE_WAIT。而在面试中可能就会问题,如果服务端有大量的TIME_WAIT和CLOSE_WAIT,应该怎么解决?

二、TIME_WAIT和CLOSE_WAIT分析

其实要想明白二者处理的方式,就首先要弄明白二者是如何产生的。下面就对TIME_WAIT和CLOSE_WAIT产生的机制进行分析:
1、TIME_WAIT
TIME_WAIT,是主动关闭方收到对方的FIN报文并回应ACK报文,在2MSL(2 倍的 max segment lifetime,最大生存时间,RFC中给出的是2分钟,但实际一般是30秒、1分钟或2分钟)后转到CLOSED状态。当然如果已经在FIN_WAIT_1状态,收到对方的FIN标志和ACK标志的报文时,可略过FIN_WAIT_2状态直接进入TIME_WAIT状态。
那如果此看来,其实TIME_WAIT并没有什么大问题啊?过了2MSL会自动回收。对,实际情况中,一般来说TIME_WAIT也很难对系统产生重大的影响。但仍然有可能在某些情况下,需要对TIME_WAIT进行处理。那TIME_WAIT的的作用是什么呢?
其实非常简单,对TCP编程来说,开发者可能都知道它是一个双工的通信过程。但实际的环境中,可能出现各种原因导致这种双工不是按理想的你问我答,你来我往的流畅的进行的。举一个简单例子,可能主动关闭端在关闭前发送了一些数据,但被动一方接到了数据后,由于各种原因没有及时的返回应答报文;或者重传的报文发送出来时,对方已经关闭等等。但是,如果此时连接被重用会是什么现象?答案很清晰,非常有可能数据被接收。那么就全乱套了,整个晋西北全乱套了。
所以TIME_WAIT的作用非常大,就是保证在规定的时间内,正常关闭连接防止数据的错误接收。如果一定要强制关闭TIME_WAIT,就需要非常清楚自己的环境是什么样子,会不会导致数据包的异常传输。也就是说,防止在短时间内,复用连接导致的数据传输问题(杜绝这种概率)。Linux内核一般MSL是30秒,所以TIME_WAIT的时间就是1分钟 ,那么1分钟之内,这个连接无法被复用,那么即使有什么重传之类的数据,对方也无法接收,保证了安全。

2、CLOSE_WAIT
CLOSE_WAIT,是被动关闭方等待关闭。即收到另一方调用close函数发送的FIN报文时,回应其ACK报文,然后转到CLOSE_WAIT状态。CLOSE_WAIT比较麻烦,因为它直接涉及到Socket句柄的释放问题。而且,它不像TIME_WAIT,过一段时间后会自己释放,它会持有非常长的时间,假如是客户端还好说一些,毕竟计算机资源保障一个客户端还是没有什么太大的麻烦。但如果是服务端可就坏了,一个实际商用的服务端,很可能有成千上万,甚至更多的Socket句柄。如果维持着大量的CLOSE_WAIT,很可能不长的时间就会资源无法分配了(想想微信这种几千万上亿的连接)。而一般情况下,很难主动关闭这些CLOSE_WAIT的句柄,所以大量CLOSE_WAIT是一个很让人难受的事情。
不过,CLOSE_WAIT产生大多是人为制造的,一般来说,在CLOSE_WAIT状态下,应该进一步发送一个FIN 包的报文给主动关闭方并等待对方的ACK同时自己进入LAST_ACK状态;如果接收到主动关闭方的ACK,则进入CLOSED状态。好,发现没有,其实就是没有发送或者对方没有收到被动方的FIN报文包。所以,大多数情况下,基本都是开发者自己的疏忽或者一些其它特殊原因导致的忘记发送FIN报文包或者主动方干脆没有关闭连接导致的。
而CLOSE_WAIT的作用也就呼之欲出,就是为了保证四次挥手的正确完成或者说等待主动方发来的中断连接的报文。

说明:网上和书籍上有大量的相关图片和状态转换的说明,还是那句话,不展开再Copy一回,有不明白的翻翻资料就明白了。

三、危害和解决

TIME_WAIT和CLOSE_WAIT出现后会引起什么问题呢?一般来说,少量的出现,对于服务器来说不会有任何的不适。但是,如果大量的出现,问题就来了,毕竟资源都是有限的,蚂蚁多了还能咬死大象呢。先看一下TIME_WAIT:
在前面提到了,TIME_WAIT在大多数情况下的危害不大,顶多占用一些不多的内存和端口,这些都可以在一段时间后自行消除。但是如果确实是需要处理怎么办?可以采用下面的办法:
打开/etc/sysctl.conf文件找到下面的值进行修改:
1、net.ipv4.tcp_tw_reuse
设置为1即开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
2、net.ipv4.tcp_tw_recycle
设置为1开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
同时使用setsockopt函数设置SO_LINGER,禁用Socket延迟关闭。不过,再次提醒,要考虑这样做的后果,不要盲目乱用。

再看一下CLOSE_WAIT:
它的危害还是非常大的,主要是对网络句柄的占用无法释放导致新连接可能无法连接上来。特别是在服务端,可能不长的时间内就会有大量的连接上来,这就非常可怕了。解决的方法就是多查看服务器上的状态,可以使用下面的命令:

netstat -na | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

如果发现大量的CLOSE_WAIT,最简单的方法就是赶紧查看自己的代码,这其实就回到了SOCKET上层应用编程中的,如何优雅的关闭SOCKET这个老生常谈的问题了。即先shutdown再close当前的SOCKET句柄,保证FIN报文包的发送。

网络通信中的状态很多,但真正常见的就几种,除了上面说的两个,另外一个就是表示连接状态的ESTABLISHED状态,其实在网络开发中,这是比较常见的几种情况。当然,这不是说别的状态没有什么意义,是一般情况下分析问题乃至的比较少而已。

四、总结

通过分析TIME_WAIT和CLOSE_WAIT的机制和产生的相关原因,就已经明白了二者的真正的意图和各种情况下的处理应对方式。这恰恰说明了,要想从根本上解决问题,一定要掌握问题的本质,并结合实际的应用场景有针对性的进行处理(调优)。凡事有好的一面,就会有不好的一面,这是基础的哲学问题。做为开发者要做的,就是尽量在开发的实际环境中扬长避短,解决实际问题而不是引入新的问题。一定不能学会只转移问题而不解决问题的风格!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值