09| TIME_WAIT是什么

发布时间:2026/7/5 8:27:19

09| TIME_WAIT是什么 引用在前面的篇幅里我们了解了 TCP 四次挥手在四次挥手的过程中发起连接断开的一方会有一段时间处于 TIME_WAIT 的状态你知道 TIME_WAIT 是用来做什么的么在面试和实战中TIME_WAIT 相关的问题始终是绕不过去的一道难题。下面就请跟随我一起找出隐藏在细节下的魔鬼吧。TIME_WAIT 发生的场景让我们先从一例线上故障说起。在一次升级线上应用服务之后我们发现该服务的可用性变得时好时坏一段时间可以对外提供服务一段时间突然又不可以大家都百思不得其解。运维同学登录到服务所在的主机上使用 netstat 命令查看后才发现主机上有成千上万处于 TIME_WAIT 状态的连接。经过层层剖析后我们发现罪魁祸首就是 TIME_WAIT。为什么呢我们这个应用服务需要通过发起 TCP 连接对外提供服务。每个连接会占用一个本地端口当在高并发的情况下TIME_WAIT 状态的连接过多多到把本机可用的端口耗尽应用服务对外表现的症状就是不能正常工作了。当过了一段时间之后处于 TIME_WAIT 的连接被系统回收并关闭后释放出本地端口可供使用应用服务对外表现为可以正常工作。这样周而复始便会出现了一会儿不可以过一两分钟又可以正常工作的现象。那么为什么会产生这么多的 TIME_WAIT 连接呢这要从 TCP 的四次挥手说起。TCP 连接终止时主机 1 先发送 FIN 报文主机 2 进入 CLOSE_WAIT 状态并发送一个 ACK 应答同时主机 2 通过 read 调用获得 EOF并将此结果通知应用程序进行主动关闭操作发送 FIN 报文。主机 1 在接收到 FIN 报文后发送 ACK 应答此时主机 1 进入 TIME_WAIT 状态。主机 1 在 TIME_WAIT 停留持续时间是固定的是最长分节生命期 MSLmaximum segment lifetime的两倍一般称之为 2MSL。和大多数 BSD 派生的系统一样Linux 系统里有一个硬编码的字段名称为TCP_TIMEWAIT_LEN其值为 60 秒。也就是说Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME- WAIT state, about 60 seconds */过了这个时间之后主机 1 就进入 CLOSED 状态。为什么是这个时间呢你可以先想一想稍后我会给出解答。你一定要记住一点只有发起连接终止的一方会进入 TIME_WAIT 状态。这一点面试的时候经常会被问到。TIME_WAIT 的作用你可能会问为什么不直接进入 CLOSED 状态而要停留在 TIME_WAIT 这个状态这要从两个方面来说。首先这样做是为了确保最后的 ACK 能让被动关闭方接收从而帮助其正常关闭。TCP 在设计的时候做了充分的容错性设计比如TCP 假设报文会出错需要重传。在这里如果图中主机 1 的 ACK 报文没有传输成功那么主机 2 就会重新发送 FIN 报文。如果主机 1 没有维护 TIME_WAIT 状态而直接进入 CLOSED 状态它就失去了当前状态的上下文只能回复一个 RST 操作从而导致被动关闭方出现错误。现在主机 1 知道自己处于 TIME_WAIT 的状态就可以在接收到 FIN 报文之后重新发出一个 ACK 报文使得主机 2 可以进入正常的 CLOSED 状态。第二个理由和连接“化身”和报文迷走有关系为了让旧连接的重复分节在网络中自然消失。我们知道在网络中经常会发生报文经过一段时间才能到达目的地的情况产生的原因是多种多样的如路由器重启链路突然出现故障等。如果迷走报文到达时发现 TCP 连接四元组源 IP源端口目的 IP目的端口所代表的连接不复存在那么很简单这个报文自然丢弃。我们考虑这样一个场景在原连接中断后又重新创建了一个原连接的“化身”说是化身其实是因为这个连接和原先的连接四元组完全相同如果迷失报文经过一段时间也到达那么这个报文会被误认为是连接“化身”的一个 TCP 分节这样就会对 TCP 通信产生影响。所以TCP 就设计出了这么一个机制经过 2MSL 这个时间足以让两个方向上的分组都被丢弃使得原来连接的分组在网络中都自然消失再出现的分组一定都是新化身所产生的。划重点2MSL 的时间是从主机 1 接收到 FIN 后发送 ACK 开始计时的如果在 TIME_WAIT 时间内因为主机 1 的 ACK 没有传输到主机 2主机 1 又接收到了主机 2 重发的 FIN 报文那么 2MSL 时间将重新计时。道理很简单因为 2MSL 的时间目的是为了让旧连接的所有报文都能自然消亡现在主机 1 重新发送了 ACK 报文自然需要重新计时以便防止这个 ACK 报文对新可能的连接化身造成干扰。TIME_WAIT 的危害过多的 TIME_WAIT 的主要危害有两种。第一是内存资源占用这个目前看来不是太严重基本可以忽略。第二是对端口资源的占用一个 TCP 连接至少消耗一个本地端口。要知道端口资源也是有限的一般可以开启的端口为 3276861000 也可以通过net.ipv4.ip_local_port_range指定如果 TIME_WAIT 状态过多会导致无法创建新连接。这个也是我们在一开始讲到的那个例子。如何优化 TIME_WAIT在高并发的情况下如果我们想对 TIME_WAIT 做一些优化来解决我们一开始提到的例子该如何办呢net.ipv4.tcp_max_tw_buckets一个暴力的方法是通过 sysctl 命令将系统值调小。这个值默认为 18000当系统中处于 TIME_WAIT 的连接一旦超过这个值时系统就会将所有的 TIME_WAIT 连接状态重置并且只打印出警告信息。这个方法过于暴力而且治标不治本带来的问题远比解决的问题多不推荐使用。调低 TCP_TIMEWAIT_LEN重新编译系统这个方法是一个不错的方法缺点是需要“一点”内核方面的知识能够重新编译内核。我想这个不是大多数人能接受的方式。SO_LINGER 的设置英文单词“linger”的意思为停留我们可以通过设置套接字选项来设置调用 close 或者 shutdown 关闭连接时的行为。int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);struct linger { int l_onoff; /* 0off, nonzeroon */ int l_linger; /* linger time, POSIX specifies units as seconds */ }设置 linger 参数有几种可能如果l_onoff为 0那么关闭本选项。l_linger的值被忽略这对应了默认行为close 或 shutdown 立即返回。如果在套接字发送缓冲区中有数据残留系统会将试着把这些数据发送出去。如果l_onoff为非 0 且l_linger值也为 0那么调用 close 后会立该发送一个 RST 标志给对端该 TCP 连接将跳过四次挥手也就跳过了 TIME_WAIT 状态直接关闭。这种关闭的方式称为“强行关闭”。 在这种情况下排队数据不会被发送被动关闭方也不知道对端已经彻底断开。只有当被动关闭方正阻塞在recv()调用上时接受到 RST 时会立刻得到一个“connet reset by peer”的异常。struct linger so_linger; so_linger.l_onoff 1; so_linger.l_linger 0; setsockopt(s,SOL_SOCKET,SO_LINGER, so_linger,sizeof(so_linger));如果l_onoff为非 0 且l_linger的值也非 0那么调用 close 后调用 close 的线程就将阻塞直到数据被发送出去或者设置的l_linger计时时间到。第二种可能为跨越 TIME_WAIT 状态提供了一个可能不过是一个非常危险的行为不值得提倡。net.ipv4.tcp_tw_reuse更安全的设置那么 Linux 有没有提供更安全的选择呢当然有。这就是net.ipv4.tcp_tw_reuse选项。Linux 系统对于net.ipv4.tcp_tw_reuse的解释如下:Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.It should not be changed without advice/request of technical experts.这段话的大意是从协议角度理解如果是安全可控的可以复用处于 TIME_WAIT 的套接字为新的连接所用。那么什么是协议角度理解的安全可控呢主要有两点只适用于连接发起方C/S 模型中的客户端对应的 TIME_WAIT 状态的连接创建时间超过 1 秒才可以被复用。使用这个选项还有一个前提需要打开对 TCP 时间戳的支持即net.ipv4.tcp_timestamps1默认即为 1。要知道TCP 协议也在与时俱进RFC 1323 中实现了 TCP 拓展规范以便保证 TCP 的高可用并引入了新的 TCP 选项两个 4 字节的时间戳字段用于记录 TCP 发送方的当前时间戳和从对端接收到的最新时间戳。由于引入了时间戳我们在前面提到的 2MSL 问题就不复存在了因为重复的数据包会因为时间戳过期被自然丢弃。总结在今天的内容里我讲了 TCP 的四次挥手重点对 TIME_WAIT 的产生、作用以及优化进行了讲解你需要记住以下三点TIME_WAIT 的引入是为了让 TCP 报文得以自然消失同时为了让被动关闭方能够正常关闭不要试图使用SO_LINGER设置套接字选项跳过 TIME_WAIT现代 Linux 系统引入了更安全可控的方案可以帮助我们尽可能地复用 TIME_WAIT 状态的连接。

相关新闻