Linux 短链timeout状态,【Linux网络编程笔记】TCP短链接产生大量TIME_WAIT致使没法对外创建新TCP链接的缘由及解决方法—基础知识篇...

最近遇到一个线上报警:服务器出现大量TIME_WAIT致使其没法与下游模块创建新HTTP链接,在解决过程当中,经过查阅经典教材和技术文章,加深了对TCP网络问题的理解。做为笔记,记录于此。

备注:本文主要介绍TCP编程中涉及到的众多基础知识,关于实际工程中对由TIME_WAIT引起的不能创建新链接问题的解决方法将在下篇笔记中给出。编程

1. 实际问题        初步查看发现,没法对外新建TCP链接时,线上服务器存在大量处于TIME_WAIT状态的TCP链接(最多的一次为单机10w+,其中引发报警的那个模块产生的TIME_WAIT约2w),致使其没法跟下游模块创建新TCP链接。

TIME_WAIT涉及到TCP释放链接过程当中的状态迁移,也涉及到具体的socket api对TCP状态的影响,下面开始逐步介绍这些概念。api

2. TCP状态迁移       面向链接的TCP协议要求每次peer间通讯前创建一条TCP链接,该链接可抽象为一个4元组(four-tuple,有时也称socket pair):(local_ip, local_port, remote_ip,remote_port),这4个元素惟一地表明一条TCP链接。

1)TCP Connection Establishment

TCP创建链接的过程,一般又叫“三次握手”(three-way handshake),可用下图来示意:

579b575e02dc4f7fbca68cae.html服务器

可对上图作以下解释:

a. client向server发送SYN并约定初始包序号(sequence number)为J;

b. server发送本身的SYN并代表初始包序号为K,同时,针对client的SYNJ返回ACKJ+1(注:J+1表示server指望的来自该client的下一个包序为J+1);

c. client收到来自server的SYN+ACK后,发送ACKK+1,至此,TCP创建成功。

其实,在TCP创建时的3次握手过程当中,还要经过SYN包商定各自的MSS,timestamp等参数,这涉及到协议的细节,本文旨在抛砖引玉,再也不展开。网络

2)TCPConnection Termination

与创建链接的3次握手相对应,释放一条TCP链接时,须要通过四步交互(又称“四次挥手”),以下图所示:

5605dd9386e6fad261b394ca2c31a0ce.png

可对上图作以下解释:

a. 链接的某一方先调用close()发起主动关闭(active close),该api会促使TCP传输层向remotepeer发送FIN包,该包代表发起active close的application再也不发送数据(特别注意:这里“再也不发送数据”的承诺是从应用层角度来看的,在TCP传输层,仍是要将该application对应的内核tcp send buffer中当前还没有发出的数据发到链路上)。remote peer收到FIN后,须要完成被动关闭(passive close),具体分为两步:b. 首先,在TCP传输层,先针对对方的FIN包发出ACK包(主要ACK的包序是在对方FIN包序基础上加1);c. 接着,应用层的application收到对方的EOF(end-of-file,对方的FIN包做为EOF传给应用层的application)后,得知这条链接不会再有来自对方的数据,因而也调用close()关闭链接,该close会促使TCP传输层发送FIN。d. 发起主动关闭的peer收到remote peer的FIN后,发送ACK包,至此,TCP链接关闭。注意1:TCP链接的任一方都可以首先调用close()以发起主动关闭,上图以client主动发起关闭作说明,而不是说只能client发起主动关闭。注意2:上面给出的TCP创建/释放链接的过程描述中,未考虑因为各类缘由引发的重传、拥塞控制等协议细节,感兴趣的同窗能够查看各类TCP RFC Documents ,好比TCP RFC793。app

3)TCP StateTransition Diagram       上面介绍了TCP创建、释放链接的过程,此处对TCP状态机的迁移过程作整体说明。将TCP RFC793中描述的TCP状态机迁移图摘出以下(下图引用自这里):

d3453b65688ee96bcbd60282319250ef.png

TCP状态机共含11个状态,状态间在各类socket apis的驱动下进行迁移,虽然此图看起来错综复杂,但对于有必定TCP网络编程经验的同窗来讲,理解起来仍是比较容易的。限于篇幅,本文不许备展开详述,想了解具体迁移过程的新手同窗,建议阅读《Linux Network Programming Volume1》第2.6节。socket

3. TIME_WAIT状态通过前面的铺垫,终于要讲到与本文主题相关的内容了。 ^_^

从TCP状态迁移图可知,只有首先调用close()发起主动关闭的一方才会进入TIME_WAIT状态,并且是必须进入(图中左下角所示的3条状态迁移线最终均要进入该状态才能回到初始的CLOSED状态)。

从图中还可看到,进入TIME_WAIT状态的TCP链接须要通过2MSL才能回到初始状态,其中,MSL是指Max

Segment Lifetime,即数据包在网络中的最大生存时间。每种TCP协议的实现方法均要指定一个合适的MSL值,如RFC1122给出的建议值为2分钟,又如Berkeley体系的TCP实现一般选择30秒做为MSL值。这意味着TIME_WAIT的典型持续时间为1-4分钟。

TIME_WAIT状态存在的缘由主要有两点:   1)为实现TCP这种全双工(full-duplex)链接的可靠释放

参考本文前面给出的TCP释放链接4次挥手示意图,假设发起active close的一方(图中为client)发送的ACK(4次交互的最后一个包)在网络中丢失,那么因为TCP的重传机制,执行passiveclose的一方(图中为server)须要重发其FIN,在该FIN到达client(client是active close发起方)以前,client必须维护这条链接的状态(尽管它已调用过close),具体而言,就是这条TCP链接对应的(local_ip, local_port)资源不能被当即释放或从新分配。直到romete peer重发的FIN达到,client也重发ACK后,该TCP链接才能恢复初始的CLOSED状态。若是activeclose方不进入TIME_WAIT以维护其链接状态,则当passive close方重发的FIN达到时,active close方的TCP传输层会以RST包响应对方,这会被对方认为有错误发生(而事实上,这是正常的关闭链接过程,并不是异常)。

2)为使旧的数据包在网络因过时而消失

为说明这个问题,咱们先假设TCP协议中不存在TIME_WAIT状态的限制,再假设当前有一条TCP链接:(local_ip, local_port, remote_ip,remote_port),因某些缘由,咱们先关闭,接着很快以相同的四元组创建一条新链接。本文前面介绍过,TCP链接由四元组惟一标识,所以,在咱们假设的状况中,TCP协议栈是没法区分先后两条TCP链接的不一样的,在它看来,这根本就是同一条链接,中间先释放再创建的过程对其来讲是“感知”不到的。这样就可能发生这样的状况:前一条TCP链接由local peer发送的数据到达remote peer后,会被该remot peer的TCP传输层当作当前TCP链接的正常数据接收并向上传递至应用层(而事实上,在咱们假设的场景下,这些旧数据到达remote peer前,旧链接已断开且一条由相同四元组构成的新TCP链接已创建,所以,这些旧数据是不该该被向上传递至应用层的),从而引发数据错乱进而致使各类没法预知的诡异现象。做为一种可靠的传输协议,TCP必须在协议层面考虑并避免这种状况的发生,这正是TIME_WAIT状态存在的第2个缘由。

具体而言,local peer主动调用close后,此时的TCP链接进入TIME_WAIT状态,处于该状态下的TCP链接不能当即以一样的四元组创建新链接,即发起active close的那方占用的local port在TIME_WAIT期间不能再被从新分配。因为TIME_WAIT状态持续时间为2MSL,这样保证了旧TCP链接双工链路中的旧数据包均因过时(超过MSL)而消失,此后,就能够用相同的四元组创建一条新链接而不会发生先后两次链接数据错乱的状况。tcp

4. socket api: close() 和 shutdown()由前面内容可知,对一条TCP链接而言,首先调用close()的一方会进入TIME_WAIT状态,除此以外,关于close()还有一些细节须要说明。

对一个tcp socket调用close()的默认动做是将该socket标记为已关闭并当即返回到调用该api进程中。此时,从应用层来看,该socket fd不能再被进程使用,即不能再做为read或write的参数。而从传输层来看,TCP会尝试将目前send buffer中积压的数据发到链路上,而后才会发起TCP的4次挥手以完全关闭TCP链接。

调用close()是关闭TCP链接的正常方式,但这种方式存在两个限制,而这正是引入shutdown()的缘由:

1)close()其实只是将socket fd的引用计数减1,只有当该socket fd的引用计数减至0时,TCP传输层才会发起4次握手从而真正关闭链接。而shutdown则能够直接发起关闭链接所需的4次握手,而不用受到引用计数的限制;

2)close()会终止TCP的双工链路。因为TCP链接的全双工特性,可能会存在这样的应用场景:local peer不会再向remote peer发送数据,而remote peer可能还有数据须要发送过来,在这种状况下,若是local peer想要通知remote peer本身不会再发送数据但还会继续收数据这个事实,用close()是不行的,而shutdown()能够完成这个任务。spa

close()和shutdown()的具体调用方法能够man查看,此处再也不赘述。server

以上就是本文要分析和解决的“因为TIME_WAIT太多致使没法对外创建新链接”问题所须要掌握的基础知识。下一篇笔记会在本文基础上介绍这个问题具体的解决方法。^_^blog

【参考资料】

1.《Linux Network Programming Volume 1》. Chapter 2 && Chapter 4

2. TCP RFC 793

3. Online Document: TCP StateTransition Diagram

================ EOF ===============

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值