为什么需要TIME_WAIT状态?为什么TIME_WAIT的时长是2*MSL?
Linux开发架构之路 2023-06-06

1. 为什么需要TIME_WAIT状态?为什么TIME_WAIT的时长是2*MSL?

原因1:防止连接关闭时四次挥手中的最后一次ACK丢失:

TCP需要保证每一包数据都可靠的到达对端,包括正常连接状态下的业务数据报文,以及用于连接管理的握手、挥手报文,这其中在四次挥手中的最后一次ACK报文比较特殊,TIME_WAIT状态就是为了应对最后一条ACK丢失的情况。

TCP保证可靠传输的前提是收发两端分别维护关于这条连接的状态信息(TCB控制块),当发生丢包时进行ARQ重传。如果连接释放了,就无法进行重传,也就无法保证发生丢包时的可靠传输。

对于最后一条ACK,如果没有TIME_WAIT状态,主动关闭一方(客户端)就会在收到对端(服务器)的FIN并回复ACK后 直接从FIN_WAIT_2 进入 CLOSED状态,并释放连接,销毁TCB实例。此时如果最后一条ACK丢失,那么服务器重传的FIN将无人处理,最后导致服务器长时间的处于 LAST_ACK状态而无法正常关闭(服务器只能等到达到FIN的最大重传次数后关闭)。

至于将TIME_WAIT的时长设置为 2MSL,是因为报文在链路中的最大生存时间为MSL(Maximum Segment Lifetime),超过这个时长后报文就会被丢弃。TIME_WAIT的时长则是:最后一次ACK传输到服务器的时间 + 服务器重传FIN 的时间,即为 2MSL。

原因2:防止新连接收到旧链接的TCP报文:

TCP使用四元组区分一个连接(源端口、目的端口、源IP、目的IP),如果新、旧连接的IP与端口号完全一致,则内核协议栈无法区分这两条连接。

2*MSL 的时间足以保证两个方向上的数据都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定是新连接上产生的。

MSL(Maximum Segment Lifetime) 报文最大生存时间在不同操作系统中的具体值:

Windows : 2 min Linux(Ubuntu) : 60 s Unix : 30 s

2. TIME_WAIT对连接并发数的影响(TIME_WAIT过多的危害):

在Linux系统中,MSL = 60 s, 2 * MSL = 120 s,所以一条待关闭的TCP连接会在 TIME_WAIT 状态等待 120秒(2分钟)。

当连接处于TIME_WAIT状态时仍会占用系统资源(fd、端口、内存),当系统的并发连接数很大时,过多的TIME_WAIT状态的连接会对系统的并发量造成影响。

(1)对服务器的影响:

由于服务器一般只需要监听一个固定的端口,所以服务器所能支持的最大并发出数的上限取决于系统套接字描述符fd的大小,以及服务器的内存大小。

fd:

Linux中一个进程 所能打开的fd的最大数量默认为 1024 个,可通过 "ulimit -n (+指定数量)" 进行修改。

Linux系统所能支持的fd最大值在 /proc/sys/fs/fd-max 文件中可以查看,系统当前的fd使用情况可以通过 /proc/sys/fs/fd-nr 查看。(本机上的fd-max的值为:1221842,即100W级。实际上fd的最大值同样也取决于系统内存的大小)

内存:

假设每一个TCP连接需要开辟 “4k的接收缓冲区 + 4k的发送缓冲区 = 8k”,1W的并发连接需要80M内存,10W并发需要800M,100W并发需要8G内存。

综上,服务器的并发数主要受限于系统内存的大小,当 TIME_WAIT 状态的连接过多时,会导致消耗的内存增加,这一点可以通过扩展服务器的内存来解决。

(2)对客户端的影响:

客户端的并发数主要受限于端口数量。

一种典型的场景是:高并发短连接(“短连接”表示“业务处理+传输数据”的时间远远小于TIME_WAIT超时的时间)。

在这种场景下,客户端可能会消耗大量的端口(例如取一个Web网页,1秒钟的HTTP短连接处理完业务数据,却需要 2分钟的TIME_WAIT等待时间,在这段时间内客户端上的这个端口是无法被其他连接使用的,如果新建连接则需要使用另外的端口号),Linux系统的最大端口为65535,除去系统使用的端口号,假设网络进程可使用的端口有 6W个,由于TIME_WAIT状态下在 2*MSL(120秒)内无法再被使用,这就限制了客户端的连接速率为 60000 / 120秒 = 500 次/秒, 这是一个非常低的并发率。

同时,大量的TIME_WAIT连接同样会消耗客户端的内存,所以客户端的最大并发数取决于 端口号与内存 二者中的最小值。

3. 优化TIME_WAIT的方法:

方法1:修改内核参数 tcp_tw_reuse:

net.ipv4.tcp_tw_reuse = 1; net.ipv4.tcp_timestamp = 1;

注意:tcp_tw_reuse 内核参数只在调用 connect() 函数时起作用,所以只能用于客户端(主动连接的一端)。

tcp_tw_reuse 的作用是:在调用connect()函数时,内核会随机找一个处于TIME_WAIT状态 超过1秒 的连接给新连接复用。(超时时间由 tcp_timestamp设置,默认为 1秒)

这种方式可以缩短 TIME_WAIT 的等待时间。

方法2:修改内核参数 tcp_max_tw_buckets:

net.ipv4.tcp_max_tw_buckets 参数的默认值为18000,当系统中处于 TIME_WAIT 状态的连接数量超过阈值,系统会将后面的TIME_WAIT连接重置。

由于这种方法会直接重置连接,因此需要谨慎使用。

方法3:设置套接字选项 SO_LINGER:

SO_LINGER选项用于设置 调用close() 关闭TCP连接时的行为,注意 SO_LINGER选项会使用RST复位报文段取代 FIN-ACK四次挥手的过程,设置了SO_LINGER选项的一方在调用close() 时会直接发送一个RST,接收端收到后复位连接,不会回复任何响应。

这样做的弊端是导致TCP缓冲区中的数据被丢弃。

正常情况下,调用close后的缺省行为是:如果有待发送的数据残留在发送缓冲区中,内核协议栈将继续将这些数据发送给接收端后才关闭连接,走正常的四次挥手流程;

设置SO_LINGER后,立即关闭连接,通过RST分组,发送缓冲区如果有未发送的数据,将会被丢弃,主动关闭的一方跳过TIME_WAIT状态,直接进入CLOSED(也跳过了FIN_WAIT_1 和 FIN_WAIT_2)。

SO_LINGER的另一种更温和的实现方式是设置一个超时时间(so_linger.l_linger),而不是直接关闭。

应用程序调用close后进入睡眠,内核协议栈负责发送缓冲中残留的待发送数据,如果在 l_linger 超时时间内发送完毕,则走正常的四次挥手流程;如果超时未发送完,则发送RST强制关闭连接,并丢弃发送缓冲区中其余的数据(接收端收到RST后也会丢弃接收缓冲区中的数据),并且发送端close()函数返回 EWOULDBLOCK。

综上,如果只是单纯为了规避 TIME_WAIT 状态,使用 SO_LINGER并不是一个好主意,因为它会在调用close关闭连接时 使用RST强制关闭连接,这可能会导致 发送缓冲区、接收缓冲区 中还未处理完的数据被丢弃。

struct linger so_linger; so_linger.l_onoff = 1; //0表示关闭,忽略l_linger的值;非0表示打开 so_linger.l_linger = 0; //设置等待时间,等于0则表示立即关闭 setsockopt(fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));

方法4:设置套接字选项 SO_REUSEADDR:

SO_REUSEADDR 选项用于通知内核:

如果端口忙,并且端口对应的TCP连接状态为TIME_WAIT,则可以重用端口;

如果端口忙,并且端口对应的TCP连接处于其他状态(非TIME_WAIT),则返回 “Address already in use” 的错误信息。

设置SO_REUSEADDR的风险是可能会导致新连接上收到旧连接的数据(复用了旧连接的端口,导致新旧连接的四元组完全一致,内核协议栈无法区分这两个连接)。

SO_REUSEADDR 选项并没像 tcp_tw_reuse 那样同时提供一个 tcp_timestamp 参数可以设置 TIME_WAIT的等待时长。

综上,对TIME_WAIT状态的优化思路是尽量缩小等待时长,而不是暴力的直接关闭(可能会引起新连接收到旧连接数据的风险),也不要直接发送RST复位连接(可能会引起发送、接收缓冲区中的数据丢失),所以使用修改内核参数 tcp_tw_reuse 参数是最保险的方式,通过根据实际网络情况和应用场景适当的调节 tcp_timestamp 的值,可以达到缩小 TIME_WAIT 等待时长,进而减少系统中同一时刻处于 TIME_WAIT 状态的连接数量的目的。




声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 【立即预约】Keysight直播:搞懂介电常数那些事儿


  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
  • 深谈嵌入式系统,嵌入式系统是如何组成的?

    嵌入式系统在生活中有诸多应用,大家对于嵌入式系统或多或少有所耳闻。在前两篇文章中,小编对嵌入式系统进行过详细介绍。为继续增进大家对嵌入式系统的认识,本文将对嵌入式系统的组成加以说明。如果你对嵌入式系...

    06-27
  • 嵌入式系统秘籍共享,最全嵌入式系统解析

    嵌入式系统的应用十分广泛,因此越来越多的人学习嵌入式系统。由此,在学习嵌入式系统之前,我们应当对嵌入式系统具备一些认识。所以在本文余下部分,小编将对嵌入式系统进行全面解析。如果你对嵌入式系统具有兴趣...

    06-27
  • 51单片机超声波测距程序详解

    51单片机超声波测距程序详解 超声波四通道测距:超声波测距实现分为三大块: 其一是12864带字库的液晶驱动程序: 代码如下: /////////////////12864驱动程序/////////////////////////// //1写数据 void WriteDat...

    06-25
  • 51系列单片机的引脚图

    51系列单片机的引脚图 端子介绍 l P0.0~P0.7 P0口8位双向口线(在引脚的39~32号端子)。 l P1.0~P1.7 P1口8位双向口线(在引脚的1~8号端子)。 l P2.0~P2.7 P2口8位双向口线(在引脚的21~28号端子)。 l P3.0~P3.7 P2口8...

    06-25
  • 51单片机串口通信需要加超时中断吗?

    接收数据时,超过一定时间就算出错. 这个超时的时间是单片机自己算出的吗?超时的时间是由编程序的人定的,他定多长就多长从一段程序开始 实现电脑向 单片机发送一些数据,单片机返回Iget +数据 #include #define u...

    06-25
  • 51单片机电机pid控制系统程序

    51单片机电机pid控制系统程序 本程序来源网上,小编进行了简单修改,还未验证。 /************************* 应用背景:直流电机的额定功率12V,额定转速3000rpm,光码盘12孔, 晶振为12MHz,定时/计数器T0检测转...

    06-25
  • 51单片机指令集设计实例

    当你编写完一个程序后,通过软件调试,你可以看到程序的指令集,从指令集你就能知道单片机内部是如何工作的,比如数据存取的寄存器与地址,下面是电工之家找的一篇指令集,讲的蛮好的。 一、数据传送类指令(7种助...

    06-25
  • 51单片机定时器工作原理及用法

    TMOD : 控制定时器的工作方式。8个bit,高四位 bit 控制 T1,、低四位 bit 控制 T0。因为定时器有4种工作方式;TMOD = 0x00(工作方式0),TMOD = 0x01(工作方式0),TMOD = 0x02(工作方式2),TMOD = 0x03(工作方式3)。...

    06-25
  • 51单片机指令系统寻址方式

    指令给出参与运算的数据方式称为寻址方式。换句话说,寻址方式就是寻找确定参与操作的数的真正地址。 在MCS-51系列单片机的指令系统中寻址方式共有7种,立即寻址方式,mcs-51单片机的一种寻址方式,操作数就写在指...

    06-25
  • 51单片机学习单片机之路总结

    学习单片机有一学期了,现在也由51转到STM32了。一直想对51的学习做一个总结。也希望对别人有一些启发。也给后学者提供一些建议。当然本文是我对自己学习过程的总结,若有不对的地方,还请高手指出。 我想,再看本...

    06-25
  • 入手STM32单片机的知识点总结

    从STM32新建工程、编译下载程序出发,让新手由浅入深,尽享STM32标准库开发的乐趣

    06-23
  • 单片机定时器之改良版:时间轮定时器

    前段时间把自己以前用的单片机定时器整理出来,我称之为简单定时器,这种简单定时器比较适合定时器使用量少的程序中,如果定时器数量要求多,精度要求高,效率就会有问题,为此,俺就实现了一个时间轮定时器,简单测...

    06-23
下载排行榜
更多
评测报告
更多
EE直播间
更多
广告