从Linux源码看TIME_WAIT状态的持续时间

news2024/11/26 19:38:34

前言

笔者一直以为在Linux下TIME_WAIT状态的Socket持续状态是60s左右。线上实际却存在TIME_WAIT超过100s的Socket。由于这牵涉到最近出现的一个复杂Bug的分析。所以,笔者就去Linux源码里面,一探究竟。

首先介绍下Linux环境

TIME_WAIT这个参数通常和五元组重用扯上关系。在这里,笔者先给出机器的内核参数设置,以免和其它问题相混淆。

cat /proc/sys/net/ipv4/tcp_tw_reuse 0
cat /proc/sys/net/ipv4/tcp_tw_recycle 0
cat /proc/sys/net/ipv4/tcp_timestamps 1

可以看到,我们设置了tcp_tw_recycle为0,这可以避免NAT下tcp_tw_recycle和tcp_timestamps同时开启导致的问题。

TIME_WAIT状态转移图

提到Socket的TIME_WAIT状态,不得就不亮出TCP状态转移图了:

 持续时间就如图中所示的2MSL。但图中并没有指出2MSL到底是多长时间,但笔者从Linux源码里面翻到了下面这个宏定义。

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
				  * state, about 60 seconds	*/

如英文字面意思所示,60s后销毁TIME_WAIT状态,那么2MSL肯定就是60s喽?

持续时间真如TCP_TIMEWAIT_LEN所定义么?

笔者之前一直是相信60秒TIME_WAIT状态的socket就能够被Kernel回收的。甚至笔者自己做实验telnet一个端口号,人为制造TIME_WAIT,自己计时,也是60s左右即可回收。

 但在追查一个问题时候,发现,TIME_WAIT有时候能够持续到111s,不然完全无法解释问题的现象。这就逼得笔者不得不推翻自己的结论,重新细细阅读内核对于TIME_WAIT状态处理的源码。当然,这个追查的问题也会写成博客分享出来,敬请期待^_^。

TIME_WAIT定时器源码

谈到TIME_WAIT何时能够被回收,不得不谈到TIME_WAIT定时器,这个就是专门用来销毁到期的TIME_WAIT Socket的。而每一个Socket进入TIME_WAIT时,必然会经过下面的代码分支:

tcp_v4_rcv
	|->tcp_timewait_state_process
		/* 将time_wait状态的socket链入时间轮
		|->inet_twsk_schedule

由于我们的kernel并没有开启tcp_tw_recycle,所以最终的调用为:

/* 这边TCP_TIMEWAIT_LEN 60 * HZ */
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
					 TCP_TIMEWAIT_LEN);

好了,让我们按下这个核心函数吧。

相关视频推荐

tcpip,accept,11个状态,细枝末节的秘密,还有哪些你不知道

10道面试必问的经典网络八股文,让你在面试中逼格满满 

6种网络模型,让你看网络代码都会似曾相识

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

 

inet_twsk_schedule

在阅读源码前,先看下大致的处理流程。Linux内核是通过时间轮来处理到期的TIME_WAIT socket,如下图所示:

 内核将60s的时间分为8个slot(INET_TWDR_RECYCLE_SLOTS),每个slot处理7.5(60/8)范围time_wait状态的socket。

void inet_twsk_schedule(struct inet_timewait_sock *tw,struct inet_timewait_death_row *twdr,const int timeo, const int timewait_len)
{
	......
	// 计算时间轮的slot
	slot = (timeo + (1 << INET_TWDR_RECYCLE_TICK) - 1) >> INET_TWDR_RECYCLE_TICK;
	......
	// 慢时间轮的逻辑,由于没有开启TCP\_TW\_RECYCLE,timeo总是60*HZ(60s)
	// 所有都走slow_timer逻辑 
	if (slot >= INET_TWDR_RECYCLE_SLOTS) {
		/* Schedule to slow timer */
		if (timeo >= timewait_len) {
			slot = INET_TWDR_TWKILL_SLOTS - 1;
		} else {
			slot = DIV_ROUND_UP(timeo, twdr->period);
			if (slot >= INET_TWDR_TWKILL_SLOTS)
				slot = INET_TWDR_TWKILL_SLOTS - 1;
		}
		tw->tw_ttd = jiffies + timeo;
		// twdr->slot当前正在处理的slot
		// 在TIME_WAIT_LEN下,这个逻辑一般7
		slot = (twdr->slot + slot) & (INET_TWDR_TWKILL_SLOTS - 1);
		list = &twdr->cells[slot];
	} else{
		// 走短时间定时器,由于篇幅原因,不在这里赘述
		......
	}
	......
	/* twdr->period 60/8=7.5 */
	if (twdr->tw_count++ == 0)
		mod_timer(&twdr->tw_timer, jiffies + twdr->period);
	spin_unlock(&twdr->death_lock);
}

从源码中可以看到,由于我们传入的timeout皆为TCP_TIMEWAIT_LEN。所以,每次刚成为的TIME_WAIT状态的socket即将链接到当前处理slot最远的slot(+7)以便处理。如下图所示:

 如果Kernel不停的产生TIME_WAIT,那么整个slow timer时间轮就会如下图所示:

 所有的slot全部挂满了TIME_WAIT状态的Socket。

具体的清理函数

每次调用inet_twsk_schedule时候传入的处理函数都是:

/*参数中的tcp_death_row即为承载时间轮处理函数的结构体*/
inet_twsk_schedule(tw,&tcp_death_row,TCP_TIMEWAIT_LEN,TCP_TIMEWAIT_LEN)
/* 具体的处理结构体 */
struct inet_timewait_death_row tcp_death_row = {
	......
	/* slow_timer时间轮处理函数 */
	.tw_timer	= TIMER_INITIALIZER(inet_twdr_hangman, 0,
					    (unsigned long)&tcp_death_row),
	/* slow_timer时间轮辅助处理函数*/
	.twkill_work	= __WORK_INITIALIZER(tcp_death_row.twkill_work,
					     inet_twdr_twkill_work),
	/* 短时间轮处理函数 */
	.twcal_timer	= TIMER_INITIALIZER(inet_twdr_twcal_tick, 0,
					    (unsigned long)&tcp_death_row),				
};

由于我们这边主要考虑的是设置为TCP_TIMEWAIT_LEN(60s)的处理时间,所以直接考察slow_timer时间轮处理函数,也就是inet_twdr_hangman。这个函数还是比较简短的:

void inet_twdr_hangman(unsigned long data)
{
	struct inet_timewait_death_row *twdr;
	unsigned int need_timer;
 
	twdr = (struct inet_timewait_death_row *)data;
	spin_lock(&twdr->death_lock);
 
	if (twdr->tw_count == 0)
		goto out;
 
	need_timer = 0;
	// 如果此slot处理的time_wait socket已经达到了100个,且还没处理完
	if (inet_twdr_do_twkill_work(twdr, twdr->slot)) {
		twdr->thread_slots |= (1 << twdr->slot);
		// 将余下的任务交给work queue处理
		schedule_work(&twdr->twkill_work);
		need_timer = 1;
	} else {
		/* We purged the entire slot, anything left?  */
		// 判断是否还需要继续处理
		if (twdr->tw_count)
			need_timer = 1;
		// 如果当前slot处理完了,才跳转到下一个slot
		twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));
	}
	// 如果还需要继续处理,则在7.5s后再运行此函数
	if (need_timer)
		mod_timer(&twdr->tw_timer, jiffies + twdr->period);
out:
	spin_unlock(&twdr->death_lock);
}

虽然简单,但这个函数里面有不少细节。第一个细节,就在inet_twdr_do_twkill_work,为了防止这个slot的time_wait过多,卡住当前的流程,其会在处理完100个time_wait socket之后就回返回。这个slot余下的time_wait会交给Kernel的work_queue机制去处理。

 值得注意的是。由于在这个slow_timer时间轮判断里面,根本不判断精确时间,直接全部删除。所以轮到某个slot,例如到了52.5-60s这个slot,直接清理52.5-60s的所有time_wait。即使time_wait还没有到60s也是如此。而小时间轮(tw_cal)会精确的判定时间,由于篇幅原因,就不在这里细讲了。

注: 小时间轮(tw\_cal)在tcp\_tw\_recycle开启的情况下会使用

先作出一个假设

我们假设,一个时间轮的数据最多能在一个slot间隔时间,也就是(60/8=7.5)内肯定能处理完毕。由于系统有tcp_tw_max_buckets设置,如果设置的比较合理,这个假设还是比较靠谱的。

注: 这里的60/8为什么需要精确到小数,而不是7。
因为实际计算的时候是拿60*HZ进行计算,
如果HZ是1024的话,那么period应该是7680,即精度精确到ms级。
所以在本文中计算的时候需要精确到小数。

如果一个slot中的TIME_WAIT<=100

如果一个slot的TIME_WAIT<=100,很自然的,我们的处理函数并不会启用work_queue。同时,还将slot+1,使得在下一个period的时候可以处理下一个slot。如下图所示:

如果一个slot中的TIME_WAIT>100

如果一个slot的TIME_WAIT>100,Kernel会将余下的任务交给work_queue处理。同时,slot不变!也即是说,下一个period(7.5s后)到达的时候,还会处理同样的slot。按照我们的假设,这时候slot已经处理完毕,那么在第7.5s的时候才将slot向前推进。也就是说,假设slot一开始为0,到真正处理slot 1需要15s!

 假设每一个slot的TIME_WAIT都>100的话,那么每个slot的处理都需要15s。

对于这种情况,笔者写了个程序进行模拟。

public class TimeWaitSimulator {
 
    public static void main(String[] args) {
        double delta = (60) * 1.0 / 8;
 
        // 0表示开始清理,1表示清理完毕
        // 清理完毕之后slot向前推进
        int startPurge = 0;
        double sum = 0;
        int slot = 0;
        while (slot < 8) {
            if (startPurge == 0) {
                sum += delta;
                startPurge = 1;
                if (slot == 7) {
                    // 因为假设进入work_queue之后,很快就会清理完
                    // 所以在slot为7的时候并不需要等最后的那个purge过程7.5s
                    System.out.println("slot " + slot + " has reach the last " + sum);
                    break;
                }
            }
            if (startPurge == 1) {
                sum += delta;
                startPurge = 0;
                System.out.println("slot " + "move to next at time " + sum);
                // 清理完之后,slot才应该向前推进
                slot++;
            }
        }
    }
}

得出结果如下面所示:

slot move to next at time 15.0
slot move to next at time 30.0
slot move to next at time 45.0
slot move to next at time 60.0
slot move to next at time 75.0
slot move to next at time 90.0
slot move to next at time 105.0
slot 7 has reach the last  112.5

也即处理到52.5-60s这个时间轮的时候,其实外面时间已经过去了112.5s,处理已经完全滞后了。不过由于TIME_WAIT状态下的Socket(inet_timewait_sock)所占用内存很少,所以不会对系统可用资源造成太大的影响。但是,这会在NAT环境下造成一个坑,这也是笔者文章前面提到过的Bug。 上面的计算如果按照图和时间线画出来,应该是这么个情况:

 也即TIME_WAIT状态的Socket在一个period(7.5s)内能处理完当前slot的情况下,最多能够存在112.5s!

如果7.5s内还处理不完,那么响应时间轮的轮转还得继续加上一个或多个perod。但在tcp_tw_max_buckets的限制,应该无法达到这么严苛的条件。

PAWS(Protection Against Wrapped Sequences)使得TIME_WAIT延长

事实上,以上结论还是不够严谨。TIME_WAIT时间还可以继续延长!看下这段源码:

enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
			   const struct tcphdr *th)
{
	......
	if (paws_reject)
		NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_PAWSESTABREJECTED);
		
	if (!th->rst) {
		/* In this case we must reset the TIMEWAIT timer.
		 *
		 * If it is ACKless SYN it may be both old duplicate
		 * and new good SYN with random sequence number <rcv_nxt.
		 * Do not reschedule in the last case.
		 */
		/* 如果有回绕校验失败的包到达的情况下,或者其实ack包 
		 * 重置定时器到新的60s之后
		 * /
		if (paws_reject || th->ack)
			inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
					   TCP_TIMEWAIT_LEN);
 
		/* Send ACK. Note, we do not put the bucket,
		 * it will be released by caller.
		 */
		/* 向对端发送当前time wait状态应该返回的ACK */
		return TCP_TW_ACK;
	}
	inet_twsk_put(tw);
	/* 注意,这边通过paws校验的包,会返回tcp_tw_success,使得time_wait状态的
	 * socket五元组也可以三次握手成功重新复用
	 * /
	return TCP_TW_SUCCESS;
}

上面的逻辑如下图所示:

 注意代码最后的return TCP_TW_SUCCESS,通过PAWS校验的包,会返回TCP_TW_SUCCESS,使得TIME_WAIT状态的Socket(五元组)也可以三次握手成功重新复用! 这段逻辑很微妙,会在笔者下一篇<<解Bug之路>>里面进行详解!

总结

如果不仔细分析就下定结论,很容就被自己之前先入为主的一些不够严谨的结论所困扰。导致排查一些复杂问题的时候将思路引导向错误的方向。笔者在追查某个问题的时候就犯了这样的错误。当种种猜测都和事实矛盾时,必须怀疑起自己之前笃定的结论并尝试着推翻它,整个过程即艰辛又快乐!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651250.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++入门,一些C++基本概念介绍

文章目录 目录 前言 1.C关键字 1.1命名空间 1.2命名空间定义 1.3命名空间的使用 2.C输入&输出 3.缺省参数 3.1缺省参数的概念 3.2缺省参数分类 4.函数重载 4.1函数重载的概念 5.引用 5.1 引用特性 5.2 常引用 5.3引用的使用场景 5.4引用和指针 6.内联函数…

【Java基础】注解——自定义注解

什么是注解? Java 注解(Annotation)又称 Java 标注&#xff0c;是 JDK5.0 引入的一种注释机制。 比如我们常见的Override和Deprecated都是注解&#xff0c;注解可以加在类、方法、成员变量等上面&#xff0c;类似于给他们“打标签"。 注解怎么定义? public interface…

华为OD机试真题 JavaScript 实现【百钱买百鸡问题】【牛客练习题】

一、题目描述 公元五世纪&#xff0c;我国古代数学家张丘建在《算经》一书中提出了“百鸡问题”&#xff1a;鸡翁一值钱五&#xff0c;鸡母一值钱三&#xff0c;鸡雏三值钱一。百钱买百鸡&#xff0c;问鸡翁、鸡母、鸡雏各几何&#xff1f; 现要求你打印出所有花一百元买一百…

AM@空间直角坐标系@数量积和向量积@向量的外积在物理学中的相关概念

文章目录 空间直角坐标系坐标面分向量坐标分解式余弦定理数量积的坐标表示公式 向量积向量积的坐标表示公式 向量的外积在物理学中的相关概念物理量ref 角速度和向量积量纲Base unit (measurement)Background&#x1f388;International System of Units&#x1f388;附 表达方…

【ROS】ROS1导航(了解)

1、简述 ROS1导航模块navigation&#xff0c;它从里程计、传感器流和目标姿势中获取信息&#xff0c;并将速度、角速度控制命令发送至差速控制单元。 因为是ROS1&#xff0c;所以下面的内容只是一带而过&#xff0c;没有深入学习总结。详细内容可参考官网&#xff1a;http://…

Spring高手之路5——彻底掌握Bean的生命周期

文章目录 1. 理解Bean的生命周期1.1 生命周期的各个阶段 2. 理解init-method和destroy-method2.1 从XML配置创建Bean看生命周期2.2 从配置类注解配置创建Bean看生命周期2.3 初始化和销毁方法的特性2.4 探究Bean的初始化流程顺序 3. PostConstruct和PreDestroy3.1 示例&#xff…

Scala入门

第1章 Scala入门 1.1 概述 Scala将面向对象和函数式编程结合成一种简洁的高级语言。 语言特点如下&#xff1a; &#xff08;1&#xff09;Scala和Java一样属于JVM语言&#xff0c;使用时都需要先编译为class字节码文件&#xff0c;并且Scala能够直接调用Java的类库。 &#…

Linux进程信号 | 信号处理

前面的文章中我们讲述了信号的产生与信号的保存这两个知识点&#xff0c;在本文中我们将继续讲述与信号处理有关的信息。 信号处理 之前我们说过在收到一个信号的时候&#xff0c;这个信号不是立即处理的&#xff0c;而是要得到的一定的时间。从信号的保存中我们可以知道如果…

CSP-J组初赛历年真题讲解第1篇

一、二进制基础 1.二进制数 00100100 和 00010100 的和是( )。 A.00101000 B.01100111 C.01000100 D.00111000 来源&#xff1a;模拟试题正确答案&#xff1a;D 讲解&#xff1a; 2.在二进制下&#xff0c;1011001()11001101011001( )1100110 A. 1011 B. 1101 C. 1010…

仓库Vuex

1. 搭建vuex仓库 1.1 安装 npm install vuexnext 1.2 引入 创建store文件夹&#xff0c;里面创建index.js&#xff0c;该js文件中写&#xff1a; import { createStore } from vuex // 引入子仓库 import model1 from "./model1.js" import model2 from "…

行为型设计模式05-备忘录模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 备忘录模式 1、备忘录模式介绍 备忘录模式是一种行为型设计模式&#xff0c;用于在不破坏封装性的前提…

Spring Resources资源操作

文章目录 1、Spring Resources概述2、Resource接口3、Resource的实现类3.1、UrlResource访问网络资源3.2、ClassPathResource 访问类路径下资源3.3、FileSystemResource 访问文件系统资源3.4、ServletContextResource3.5、InputStreamResource3.6、ByteArrayResource 4、Resour…

H桥级联型五电平三相逆变器MATLAB仿真模型

H桥级联型五电平逆变器MATLAB仿真模型资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87899094 模型简介&#xff1a; MATLAB21b版本 逆变器采用H桥级联的形式连接&#xff0c;加设LCL滤波器&#xff0c;三相负载构成主电路。 采用SPWM调制&#xff0c;可…

不宜使用Selenium自动化的10个测试场景

尽管在很多情况下测试自动化是有意义的&#xff0c;但一些测试场景是不应该使用自动化测试工具的&#xff0c;比如Selenium、WebDriver。 下面有10个示例&#xff0c;来解释为什么自动化在这种情况下使用时没有意义的&#xff0c;我还将为您提供每种方法的替代方法。 01.验证…

TreeView 简单使用

本文主要介绍 QML 中 TreeView 的基本使用方法&#xff0c;包括&#xff1a;TreeView的适用场景&#xff1b; 控件简介 QML TreeView 是 Qt Quick 中的一个组件&#xff0c;用于显示树形结构的数据。它提供了一种以层次结构方式展示数据的方式&#xff0c;其中每个节点可以包含…

ESP32学习之定时器和PWM

一.定时器代码如下&#xff1a; #include <Arduino.h>hw_timer_t *timer NULL; int interruptCounter 0;// 函数名称&#xff1a;onTimer() // 函数功能&#xff1a;中断服务的功能&#xff0c;它必须是一个返回void&#xff08;空&#xff09;且没有输入参数的函数 //…

【动态规划】路径问题

冻龟算法系列之路径问题 文章目录 【动态规划】路径问题1. 不同路径1.1 题目解析1.2 算法原理1.2.1 状态表示1.2.2 状态转移方程1.2.3 初始化1.2.4 填表顺序1.2.5 返回值 1.3 编写代码 2. 不同路径Ⅱ2.1 题目解析2.2 算法原理2.2.1 状态表示2.2.2 状态转移方程2.2.3 初始化2.2.…

性能测试学习之数据驱动性能测试

了解数据驱动测试理念、能够如何在jmeter中用多种方式实现数据驱动测试。 知识点&#xff1a;字符串拼接、计数器、循环控制器 1. 数据驱动的理念 1.1 定义 从数据文件中读取测试数据,驱动测试过程的一-种测试方法数据驱动可以理解为更高级的参数化 1.2 特点 测试数据与测试…

【Linux】socket 编程(socket套接字介绍、字节序、socket地址、IP地址转换函数、套接字函数、TCP通信实现)

目录 1、socket套接字介绍2、字节序简介字节序转换函数 3、socket地址专用socket地址 4、IP地址转换函数5、套接字函数6、TCP通信实现&#xff08;服务器端和客户端&#xff09; 橙色 1、socket套接字介绍 所谓套接字&#xff0c;就是对网络中不同主机上的应用进程之间进行双…

深入理解深度学习——Transformer:整合编码器(Encoder)和解码器Decoder)

分类目录&#xff1a;《深入理解深度学习》总目录 相关文章&#xff1a; 注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;基础知识 注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;注意力汇聚与Nadaraya-Watson核回归 注意力机制&…