wait代码写在哪里(系统编程你所不知道的TIME)

作者:大房 链接:http://mp.weixin.qq.com/s/YAJb-xFbftKS9Dn5LBIj7w

本文针对对TCP/IP协议有所了解,至少入门的同学。如果你连TCP/IP协议是啥,或者只听到过名字,那看起来会很吃力。

你遇到过TIME_WAIT的问题吗

我相信很多都遇到过这个问题。一旦有用户在喊:网络变慢了。第一件事情就是,netstat -a | grep TIME_WAIT | wc -l 一下。哎呀妈呀,几千个TIME_WAIT.

然后,做的第一件事情就是:打开Google或者Bing,输入关键词:too many time wait。一定能找到解决方案,而排在最前面或者被很多人到处转载的解决方案一定是打开 sysctl.conf 文件,修改以下几个参数:

1:net.ipv4.tcp_tw_recycle = 1

2:net.ipv4.tcp_tw_reuse = 1

3:net.ipv4.tcp_timestamps = 1

你也会被告知,开启tw_recylce和tw_reuse一定需要timestamps的支持,而且这些配置一般不建议开启,但是对解决TIME_WAIT很多的问题,有很好的用处。

接下来,你就直接修改了这几个参数,reload一下,发现,咦,没几分钟,TIME_WAIT的数量真的降低了,也没发现哪个用户说有问题,然后就没有然后了。

做到这一步,相信50%或者更高比例的开发就已经止步了。问题好像解决了,但是,要彻底理解并解决这个问题,可能就没这么简单,或者说,还有很长的路要走!

什么是TIME_WAIT和close_WAIT

所谓,要解决问题,就要先理解问题。随便改两行代码,发现bug“没有了”,也不是bug真的没有了,只是隐藏在更深的地方,你没有发现,或者以你的知识水平,你无法发现而已。

大家知道,由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的。

1:主动关闭连接的一方,调用close();协议层发送FIN包

2:被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方 等待 被动关闭一方的应用程序,调用close操作

3:被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态;

4:主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态

5:等待2MSL时间,主动关闭的一方,结束TIME-WAIT,进入CLOSED状态

通过上面的一次socket关闭操作,你可以得出以下几点:

1:主动关闭连接的一方 - 也就是主动调用socket的close操作的一方,最终会进入TIME_WAIT状态

2:被动关闭连接的一方,有一个中间状态,即CLOSE_WAIT,因为协议层在等待上层的应用程序,主动调用close操作后才主动关闭这条连接

3:TIME_WAIT会默认等待2MSL时间后,才最终进入CLOSED状态;

4:在一个连接没有进入CLOSED状态之前,这个连接是不能被重用的!

所以,这里凭你的直觉,TIME_WAIT并不可怕(not really,后面讲),CLOSE_WAIT才可怕,因为CLOSE_WAIT很多,表示说要么是你的应用程序写的有问题,没有合适的关闭socket;要么是说,你的服务器CPU处理不过来(CPU太忙)或者你的应用程序一直睡眠到其它地方(锁,或者文件I/O等等),你的应用程序获得不到合适的调度时间,造成你的程序没法真正的执行close操作。

这里又出现两个问题:

1:上文提到的连接重用,那连接到底是个什么概念?

2:协议层为什么要设计一个TIME_WAIT状态?这个状态为什么默认等待2MSL时间才会进入CLOSED

先解释清楚这两个问题,我们再来看,开头提到的几个网络配置究竟有什么用,以及TIME_WAIT的后遗症问题。

Socket连接到底是个什么概念

大家经常提socket,那么,到底什么是一个socket?其实,socket就是一个 五元组,包括:源IP、源端口、目的IP、目的端口、类型:TCP或者UDP

这个五元组,即标识了一条可用的连接。注意,有很多人把一个socket定义成四元组,也就是 源IP:源端口 目的IP:目的端口,这个定义是不正确的。

例如,如果你的本地出口IP是180.172.35.150,那么你的浏览器在连接某一个Web服务器,例如百度的时候,这条socket连接的四元组可能就是:

[180.172.35.150:45678, tcp, 180.97.33.108:80]

源IP为你的出口IP地址 180.172.35.150,源端口为随机端口 45678,目的IP为百度的某一个负载均衡服务器IP 180.97.33.108,端口为HTTP标准的80端口。

如果这个时候,你再开一个浏览器,访问百度,将会产生一条新的连接:

[180.172.35.150:43678, tcp, 180.97.33.108:80]

这条新的连接的源端口为一个新的随机端口 43678。

TIME_WAIT有什么用

如果我们来做个类比的话,TIME_WAIT的出现,对应的是你的程序里的异常处理,它的出现,就是为了解决网络的丢包和网络不稳定所带来的其他问题:

第一,防止前一个连接【五元组,我们继续以180.172.35.150:45678, tcp, 180.97.33.108:80 为例】上延迟的数据包或者丢失重传的数据包,被后面复用的连接【前一个连接关闭后,此时你再次访问百度,新的连接可能还是由180.172.35.150:45678, tcp, 180.97.33.108:80 这个五元组来表示,也就是源端口凑巧还是45678】错误的接收(异常:数据丢了,或者传输太慢了),参见下图:

wait代码写在哪里(系统编程你所不知道的TIME)(1)

第二,确保连接方能在时间范围内,关闭自己的连接。其实,也是因为丢包造成的,参见下图:

wait代码写在哪里(系统编程你所不知道的TIME)(2)

  • 主动关闭方关闭了连接,发送了FIN;
  • 被动关闭方回复ACK同时也执行关闭动作,发送FIN包;此时,被动关闭的一方进入LAST_ACK状态
  • 主动关闭的一方回去了ACK,主动关闭一方进入TIME_WAIT状态;
  • 但是最后的ACK丢失,被动关闭的一方还继续停留在LAST_ACK状态
  • 此时,如果没有TIME_WAIT的存在,或者说,停留在TIME_WAIT上的时间很短,则主动关闭的一方很快就进入了CLOSED状态,也即是说,如果此时新建一个连接,源随机端口如果被复用,在connect发送SYN包后,由于被动方仍认为这条连接【五元组】还在等待ACK,但是却收到了SYN,则被动方会回复RST
  • 造成主动创建连接的一方,由于收到了RST,则连接无法成功

所以,你看到了,TIME_WAIT的存在是很重要的,如果强制忽略TIME_WAIT,还是有很高的机率,造成数据粗乱,或者短暂性的连接失败。

那么,为什么说,TIME_WAIT状态会是持续2MSL(2倍的max segment lifetime)呢?这个时间可以通过修改内核参数调整吗?第一,这个2MSL,是RFC 793里定义的,参见RFC的截图标红的部分:

wait代码写在哪里(系统编程你所不知道的TIME)(3)

这个定义,更多的是一种保障(IP数据包里的TTL,即数据最多存活的跳数,真正反应的才是数据在网络上的存活时间),确保最后丢失了ACK,被动关闭的一方再次重发FIN并等待回复的ACK,一来一去两个来回。内核里,写死了这个MSL的时间为:30秒,所以TIME_WAIT的即为1分钟:

wait代码写在哪里(系统编程你所不知道的TIME)(4)

所以,再次回想一下前面的问题,如果一条连接,即使在四次握手关闭了,由于TIME_WAIT的存在,这个连接,在1分钟之内,也无法再次被复用,那么,如果你用一台机器做压测的客户端,你一分钟能发送多少并发连接请求?如果这台是一个负载均衡服务器,一台负载均衡服务器,一分钟可以有多少个连接同时访问后端的服务器呢?

写到这里,也许你稍微理解了TIME_WAIT和CLOSE_WAIT的区别。下篇,就针对文章的问题,稍作解答,然后讲解一下tw_reuse和tw_recyle的区别。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页