侧边栏壁纸
  • 累计撰写 146 篇文章
  • 累计创建 21 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

TCP 断开后,为什么还要等?

YaFuX
2026-06-23 / 0 评论 / 0 点赞 / 1 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言

假设 Java 程序频繁调用远程服务:

for (int i = 0; i < 100_000; i++) {
    try (Socket socket = new Socket(host, port)) {
        sendRequest(socket);
        readResponse(socket);
    }
}

刚开始一切正常。

运行一段时间后,却突然出现:

java.net.BindException:
Cannot assign requested address

查看 Linux 网络状态:

ss -ant state time-wait

发现系统中存在几万条:

TIME-WAIT

这时很容易产生一连串疑问:

Java 已经调用 close(),连接为什么还没有彻底消失?

TIME_WAIT 到底在等待什么?

为什么通常是主动关闭连接的一方进入 TIME_WAIT?

TIME_WAIT 占用的是端口、文件描述符,还是内存?

服务端出现大量 TIME_WAIT,还能继续监听原来的端口吗?

为什么短连接容易耗尽客户端端口?

TIME_WAIT 和 CLOSE_WAIT 有什么区别?

修改 tcp_fin_timeout 能缩短 TIME_WAIT 吗?

tcp_tw_reuse 能不能直接打开?

网上常见的 tcp_tw_recycle 为什么在新系统里找不到?

TIME_WAIT 并不是 TCP 忘记释放资源,也不是内核设计失误。

恰恰相反:

TIME_WAIT 是 TCP 为可靠关闭连接、隔离旧报文而主动保留的一段安全时间。

要理解它,必须先看 TCP 连接是怎样关闭的。


一、关闭不是一次动作

TCP 是全双工协议。

一条连接实际上包含两个方向:

客户端 ──────→ 服务端
客户端 ←────── 服务端

一个方向停止发送,并不代表另一个方向也已经关闭。

因此,TCP 的正常关闭通常需要双方分别发送 FIN。

1. 四次挥手

假设客户端主动关闭:

客户端                                      服务端

ESTABLISHED                               ESTABLISHED
    │                                          │
    │  FIN:我没有数据要发了                   │
    ├─────────────────────────────────────────→│
    │                                          │
FIN_WAIT_1                                CLOSE_WAIT
    │                                          │
    │  ACK:我知道了                           │
    │←─────────────────────────────────────────┤
    │                                          │
FIN_WAIT_2                                     │
    │                                          │
    │                 服务端处理剩余数据        │
    │                                          │
    │  FIN:我也没有数据要发了                 │
    │←─────────────────────────────────────────┤
    │                                          │
TIME_WAIT                                  LAST_ACK
    │                                          │
    │  ACK:我知道了                           │
    ├─────────────────────────────────────────→│
    │                                          │
    │                                       CLOSED
    │
等待 2MSL
    │
 CLOSED

第一次 FIN 只代表:

客户端不再向服务端发送数据

服务端仍然可以继续向客户端发送数据。

这就是 TCP 的半关闭能力。Java 的 Socket.shutdownOutput() 也只关闭输出方向:之前写入的数据会继续发送,随后才执行 TCP 的正常终止流程。

2. close 由内核完成

Java 执行:

socket.close();

并不是 Java 自己构造 FIN 报文。

完整链路是:

Java Socket.close()
        ↓
JVM 调用操作系统接口
        ↓
Linux TCP 协议栈处理关闭
        ↓
发送 FIN
        ↓
维护 TCP 状态机

Java 对象已经不可再使用,不代表内核中的 TCP 状态立即完全删除。

应用对象、文件描述符和 TCP 协议状态,属于不同层次。


二、谁进入 TIME_WAIT?

最常见的规律是:

主动发起正常关闭,并最终发送最后一个 ACK 的一方进入 TIME_WAIT。

在上面的例子中,客户端先发送 FIN,所以客户端进入 TIME_WAIT。

但不能简单记成:

客户端一定进入 TIME_WAIT
服务端一定进入 CLOSE_WAIT

真正决定状态的是:

谁先发起关闭

1. 客户端主动关闭

客户端先发送 FIN
        ↓
客户端进入 TIME_WAIT

常见于:

短连接客户端
爬虫
压测程序
频繁调用下游的服务

2. 服务端主动关闭

服务端先发送 FIN
        ↓
服务端进入 TIME_WAIT

常见于:

服务端主动淘汰空闲连接
协议要求服务端先关闭
请求处理完成后服务端主动断开
负载均衡器主动回收后端连接

因此,服务端出现大量 TIME_WAIT 并不奇怪。

它说明服务端在大量连接中承担了主动关闭方。

3. 同时关闭

如果双方几乎同时发送 FIN,双方都可能经过:

FIN_WAIT_1
    ↓
CLOSING
    ↓
TIME_WAIT

RFC 9293 的同时关闭流程中,两端最终都会进入 TIME-WAIT。

所以更准确的结论是:

正常情况下,主动关闭方进入 TIME_WAIT;同时关闭时,双方都可能进入 TIME_WAIT。


三、为什么要等?

TIME_WAIT 主要解决两个问题:

保证最后一个 ACK 可重传
防止旧连接报文污染新连接

1. 重发最后的 ACK

连接关闭的最后两步是:

服务端 ── FIN ──→ 客户端
服务端 ←─ ACK ─── 客户端

客户端发送最后一个 ACK 后,不能立即删除全部连接状态。

因为这个 ACK 可能丢失:

服务端 ── FIN ──→ 客户端
服务端 ←─ ACK ──X  报文丢失

服务端没有收到 ACK,会认为自己的 FIN 没有被确认,于是重新发送:

服务端 ── FIN 重传 ──→ 客户端

如果客户端已经完全忘记这条连接,它就无法正确识别这个 FIN,也无法再次发送 ACK。

保留 TIME_WAIT 后:

收到重传 FIN
    ↓
识别为刚刚关闭的连接
    ↓
再次发送 ACK

这能帮助被动关闭方顺利从 LAST_ACK 进入 CLOSED。

2. 隔离旧报文

TCP 连接通常由四元组区分:

源 IP
源端口
目标 IP
目标端口

例如:

10.0.0.5:50000
        ↓
10.0.0.10:8080

连接关闭后,网络中可能仍然存在延迟、重传或乱序的旧报文。

假设系统立即用完全相同的四元组创建新连接:

旧连接:
10.0.0.5:50000 → 10.0.0.10:8080

新连接:
10.0.0.5:50000 → 10.0.0.10:8080

一个迟到的旧报文可能被误认为属于新连接。

TIME_WAIT 会为旧连接保留一段隔离期:

旧连接关闭
    ↓
四元组进入 TIME_WAIT
    ↓
旧报文逐渐从网络中消失
    ↓
四元组才可以安全复用

RFC 9293 要求主动关闭的连接停留在 TIME-WAIT 状态中,规范模型中的等待时间为两倍 MSL,即 2 × Maximum Segment Lifetime

不同操作系统的具体实现时间可能不同,排查时应以当前系统实际状态和内核实现为准,而不是把某个固定秒数当作所有平台的统一标准。


四、TIME_WAIT 占什么?

TIME_WAIT 经常被描述成:

端口被占用

这个说法过于粗糙。

它真正保留的是:

一条已关闭连接的协议状态
+
对应连接标识的使用约束

1. 不占应用 fd

应用执行 close() 后,原来的文件描述符已经不再供应用使用。

例如:

Java 进程
fd 7 → Socket

执行关闭后:

Java 进程
fd 7 → 已释放,可被其他文件重新使用

但 Linux 内核仍然保留一个更轻量的 TIME-WAIT 状态对象,用于识别旧连接报文并完成协议等待。

所以可能出现:

lsof 中看不到对应应用 fd
但 ss 中仍能看到 TIME-WAIT

Linux 还通过 tcp_max_tw_buckets 限制系统同时保存的 TIME-WAIT 状态数量,说明这些状态仍然需要消耗内核资源。

2. 不独占服务端端口

假设服务端监听:

0.0.0.0:8080

系统中存在:

10.0.0.10:8080 ↔ 10.0.0.1:50001
10.0.0.10:8080 ↔ 10.0.0.2:50001
10.0.0.10:8080 ↔ 10.0.0.3:50001

这些连接的服务端端口都是 8080,但远端地址不同,所以四元组不同。

其中一条进入 TIME_WAIT:

10.0.0.10:8080 ↔ 10.0.0.1:50001

不代表服务端不能继续接受:

10.0.0.10:8080 ↔ 10.0.0.4:50001

服务端的监听 Socket 与每条已建立连接是不同的内核对象。

因此:

存在大量本地端口为 8080 的 TIME_WAIT

不等于:

8080 端口完全不可使用

3. 限制的是连接标识

更准确的说法是:

TIME_WAIT 会影响某些相同或冲突连接标识的立即复用,而不是把一个端口对所有网络通信永久锁死。

具体能否复用,还受到:

本地与远端地址
主动还是被动连接
bind 与 connect 的调用方式
SO_REUSEADDR
内核复用策略
TCP 时间戳
网络命名空间

等因素影响。

不能只看到一个 TIME_WAIT,就判断“端口已经被占死”。


五、端口何时耗尽?

TIME_WAIT 最容易影响的是高频建立短连接的主动连接方。

1. 临时端口

客户端没有显式绑定本地端口时,内核会从临时端口范围中选择一个端口。

可以查看:

cat /proc/sys/net/ipv4/ip_local_port_range

Linux 主线文档中的默认范围是:

32768 60999

但发行版和系统配置可能不同。

可用端口数可以粗略计算为:

60999 - 32768 + 1
= 28232

2. 同一目标的短连接

假设客户端持续连接同一个目标:

客户端 IP:10.0.0.5
服务端 IP:10.0.0.10
服务端端口:8080

每条连接主要通过不同的本地临时端口区分:

10.0.0.5:40001 → 10.0.0.10:8080
10.0.0.5:40002 → 10.0.0.10:8080
10.0.0.5:40003 → 10.0.0.10:8080

如果客户端快速创建并主动关闭连接:

创建连接
    ↓
发送请求
    ↓
关闭连接
    ↓
本地四元组进入 TIME_WAIT

短时间内就可能积累大量尚不可直接复用的端口组合。

可以用一个简化公式估算:

TIME_WAIT 数量
≈
每秒主动关闭连接数
×
TIME_WAIT 保留时间

例如,每秒主动关闭 1000 条连接:

1000 × 30 秒
= 30000 条

数量就已经接近典型临时端口范围的规模。

这只是粗略估算,实际结果还受内核复用策略、目标数量、源 IP 数量和连接分布影响。

3. 多个目标可以扩展

以下连接可以使用相同的本地端口:

10.0.0.5:40001 → 10.0.0.10:8080
10.0.0.5:40001 → 10.0.0.11:8080

因为目标 IP 不同,四元组不同。

同理,增加源 IP 也可以扩大可用四元组空间:

10.0.0.5:40001 → 10.0.0.10:8080
10.0.0.6:40001 → 10.0.0.10:8080

所以:

65535 个端口

并不是一台主机 TCP 连接总数的绝对上限。

限制取决于四元组空间及系统资源,而不是只取决于服务端端口数量。

4. 服务端也可能受影响

服务端通常使用固定监听端口,不会因为每条入站连接再分配一个新的监听端口。

但如果服务端同时也是下游客户端,例如:

网关
反向代理
微服务调用方
数据库代理

它建立大量出站短连接时,同样可能耗尽自己的临时端口。

因此排查时必须区分:

入站监听连接
出站主动连接

不能只因为问题发生在“服务器机器”上,就认为端口耗尽来自监听端口。


六、这算攻击吗?

如果一个测试程序疯狂创建短连接,最终耗尽自己的临时端口:

压测程序无法再建立连接
其他客户端仍能正常访问服务

这通常只是:

客户端自身资源耗尽

不能直接称为 DDoS。

DDoS 强调多个来源或分布式流量共同消耗目标服务资源。

但也不能得出:

TIME_WAIT 永远不会造成 DoS

大量连接关闭仍可能消耗服务端的:

TIME-WAIT 状态表
内核内存
哈希表处理能力
CPU
NAT 或连接跟踪表

Linux 的 tcp_max_tw_buckets 本身就被描述为防止简单拒绝服务的一项资源保护限制。

所以应根据受影响的资源判断:

仅攻击者本机端口耗尽
    更接近自我资源耗尽

服务端 TIME_WAIT 状态被大规模消耗
    可能形成拒绝服务风险

不能只根据“其他用户还能不能连接”给攻击类型下绝对结论。


七、CLOSE_WAIT 的区别

TIME_WAIT 和 CLOSE_WAIT 都出现在连接关闭过程中,但含义完全不同。

1. TIME_WAIT

谁产生:
通常是主动关闭方

为什么存在:
等待重传 FIN
隔离旧连接报文

是否会自动消失:
正常情况下会

主要由谁管理:
TCP 协议栈

2. CLOSE_WAIT

谁产生:
收到对方 FIN 的被动关闭方

为什么存在:
对端不再发送数据
但本地应用还没有关闭自己的方向

是否会自动消失:
不一定

主要由谁负责:
应用程序

状态过程:

对端发送 FIN
    ↓
本地内核回复 ACK
    ↓
本地进入 CLOSE_WAIT
    ↓
等待应用调用 close()
    ↓
发送 FIN
    ↓
进入 LAST_ACK

如果应用始终不调用 close()

CLOSE_WAIT
    ↓
长期保留

因此,大量长期存在的 CLOSE_WAIT 通常说明:

Socket 没有关闭
异常路径漏关资源
线程卡住
业务处理没有结束
连接对象仍被引用

3. 一句话区分

TIME_WAIT:
内核还在等协议安全时间

CLOSE_WAIT:
内核在等应用调用 close()

遇到 CLOSE_WAIT,优先查应用代码。

遇到 TIME_WAIT,先分析连接创建和关闭模式。


八、如何观察?

1. 查看汇总

ss -s

可以查看:

established
closed
orphaned
timewait

等汇总信息。

2. 查看 TIME_WAIT

ss -ant state time-wait

统计数量:

ss -ant state time-wait |
    tail -n +2 |
    wc -l

3. 查看 CLOSE_WAIT

ss -ant state close-wait

CLOSE_WAIT 通常仍与应用 Socket 相关,可以结合:

ss -antp state close-wait

或:

lsof -nP -iTCP

定位进程。

4. 查看临时端口

cat /proc/sys/net/ipv4/ip_local_port_range

5. 查看相关参数

sysctl net.ipv4.tcp_tw_reuse
sysctl net.ipv4.tcp_tw_reuse_delay
sysctl net.ipv4.tcp_max_tw_buckets
sysctl net.ipv4.tcp_fin_timeout

6. 结合方向判断

不要只统计总数量。

还需要关注:

本地地址和端口
远端地址和端口
哪一端主动关闭
是否集中访问同一个下游
是否使用连接池
连接创建速率
TIME_WAIT 增长速率

例如:

本地随机端口 → 固定远端 8080

通常说明本机作为客户端频繁创建出站连接。

而:

本地固定 8080 → 大量远端随机端口

说明本机作为服务端,并且很可能由本机主动关闭连接。


九、怎么治理?

处理 TIME_WAIT 的第一选择,不应该是修改内核参数。

更合理的顺序是:

先改连接模型
再扩展端口空间
最后评估内核参数

1. 复用连接

最有效的方法通常是减少连接创建次数。

从:

每个请求:
连接 → 请求 → 关闭

改成:

建立连接
    ↓
发送多个请求
    ↓
连接空闲后再关闭

常见方式包括:

HTTP Keep-Alive
HTTP/2 多路复用
数据库连接池
Redis 连接池
RPC 长连接
消息队列长连接

如果一条连接能承载 1000 次请求,那么连接关闭次数理论上可以下降几个数量级。

2. 使用连接池

错误模式:

public Response request(Request request) throws IOException {
    try (Socket socket = new Socket(host, port)) {
        return execute(socket, request);
    }
}

每次调用都创建和关闭连接。

更合理的模型是:

连接池
    ↓
借出连接
    ↓
执行请求
    ↓
归还连接

不过连接池还需要处理:

最大连接数
空闲超时
连接存活检测
请求超时
异常连接淘汰
服务端关闭

不能只创建一个永久不关闭的 Socket。

3. 调整协议关闭方

如果某个服务总是主动关闭,它就会承担更多 TIME_WAIT。

可以根据协议语义,让更适合承担该状态的一方主动关闭。

例如:

服务端发送完整响应
    ↓
客户端确认读取完成
    ↓
客户端主动关闭

但这只是转移 TIME_WAIT,不是消除 TIME_WAIT。

更不能为了减少服务端 TIME_WAIT,就设计出无法正确回收的长连接。

4. 扩展端口空间

可以检查并合理调整:

ip_local_port_range

但扩大端口范围只是增加容量,不能解决不合理的短连接模型。

Linux 文档还提供 ip_local_reserved_ports,用于避免自动分配与业务保留端口冲突。

5. 增加源 IP

高强度出站代理或压测系统,可以使用多个源 IP 扩展四元组空间:

源 IP A × 临时端口范围
源 IP B × 临时端口范围
源 IP C × 临时端口范围

这是一种容量扩展方案,但系统仍需要评估:

路由
策略路由
NAT
负载均衡
连接跟踪
网卡与带宽

6. 水平扩容

如果单机出站连接速率已经接近极限,可以把连接分散到多台机器。

但扩容前仍应确认瓶颈真的是:

临时端口或 TIME_WAIT

而不是:

CPU
文件描述符
下游连接上限
NAT 表
线程池
业务超时

十、参数别乱改

1. tcp_tw_reuse

当前 Linux 内核文档中:

net.ipv4.tcp_tw_reuse = 0
    禁用

net.ipv4.tcp_tw_reuse = 1
    全局启用

net.ipv4.tcp_tw_reuse = 2
    仅对回环流量启用

它允许在协议判断安全时,为新连接复用 TIME-WAIT Socket。当前主线文档还明确提醒,不应在没有专业评估的情况下修改该参数。

部分内核还提供:

tcp_tw_reuse_delay

用于控制启用复用后,TIME-WAIT 连接至少经过多长延迟才可被复用。该参数与 TCP 时间戳和 PAWS 机制有关,设置得低于对端时间戳时钟粒度可能破坏安全判断。

因此,不应该机械地执行:

sysctl -w net.ipv4.tcp_tw_reuse=1

然后认为问题已经解决。

先确认:

内核版本
发行版默认值
连接方向
是否经过 NAT
是否启用时间戳
是否确实发生端口耗尽

2. tcp_max_tw_buckets

net.ipv4.tcp_max_tw_buckets

控制系统允许同时保存的 TIME-WAIT 状态上限。

超过限制后,内核可能立即销毁额外的 TIME-WAIT 状态并输出警告。

Linux 文档明确说明,这个限制用于防御简单 DoS,不应为了“减少 TIME_WAIT”而人为调低;如果正常网络条件确实需要更多状态,应在评估内存后考虑提高。

所以:

把上限调低

不是优化,而可能是在降低 TCP 的协议保护能力。

3. tcp_fin_timeout

这是最常见的误区。

net.ipv4.tcp_fin_timeout

控制的是:

无应用引用的 FIN_WAIT_2 状态

等待对端关闭的时间。

它不是 TIME_WAIT 定时器。Linux 官方文档对该参数的定义就是孤儿连接在 FIN_WAIT_2 中保留的时间。

因此:

sysctl -w net.ipv4.tcp_fin_timeout=10

不能被解释为:

把 TIME_WAIT 缩短到 10 秒

4. tcp_tw_recycle

很多旧博客会建议:

net.ipv4.tcp_tw_recycle = 1

不要直接照抄。

当前主线 Linux 的网络参数文档已经不再提供这一配置项,而是保留 tcp_tw_reusetcp_tw_reuse_delay 等机制。

面对旧调优文章,首先应在当前系统中确认:

sysctl -a | grep tcp_tw

不存在的参数,不应该被写进现代生产环境配置。

5. SO_REUSEADDR

SO_REUSEADDR 主要影响本地地址的绑定规则。

它常用于服务端重启后重新绑定监听地址,但它不等于:

复用所有 TIME_WAIT 出站连接

也不等于:

自动解决客户端临时端口耗尽

Linux 当前文档甚至专门区分了普通 SO_REUSEADDR、自动端口选择以及 ip_autobind_reuse 的行为,并提醒后者可能破坏部分应用。

需要区分:

SO_REUSEADDR
    Socket 地址绑定语义

tcp_tw_reuse
    TIME-WAIT 新连接复用策略

它们不是同一个开关。


十一、Java 如何关闭?

1. 始终释放 Socket

服务端处理连接时,应保证所有路径都能关闭:

private void handle(Socket client) {
    try (client;
         InputStream input = client.getInputStream();
         OutputStream output = client.getOutputStream()) {

        process(input, output);

    } catch (IOException exception) {
        log.error("Socket processing failed", exception);
    }
}

try-with-resources 可以覆盖:

正常返回
读取异常
写入异常
业务异常

避免 Socket 因异常路径遗漏关闭,从而长期停留在 CLOSE_WAIT。

2. 区分半关闭

有些协议使用 EOF 表示请求发送完毕。

客户端可以调用:

socket.shutdownOutput();

表示:

我不再发送数据
但仍然可以接收响应

示例:

try (Socket socket = new Socket()) {
    socket.connect(
            new InetSocketAddress(host, port),
            3000
    );

    OutputStream output = socket.getOutputStream();
    output.write(requestBytes);
    output.flush();

    socket.shutdownOutput();

    byte[] response =
            socket.getInputStream().readAllBytes();

    process(response);
}

Java 文档说明,shutdownOutput() 会先发送此前写入的数据,再执行 TCP 的正常终止序列。

不过,是否应该使用半关闭取决于应用协议。

HTTP、数据库协议和 RPC 框架通常有自己的消息边界,不能随意用 EOF 代替协议结束标志。

3. 不要暴力发送 RST

为了避免 TIME_WAIT,有人会尝试:

设置 SO_LINGER 为 0
立即关闭 Socket

这类做法可能把正常 FIN 关闭变成异常 RST 终止。

结果可能包括:

尚未发送的数据丢失
对端收到 Connection reset
应用层无法确认响应是否完整
连接恢复逻辑异常

TIME_WAIT 不是应该被不惜代价消灭的错误状态。

可靠关闭优先于表面上减少状态数量。


十二、常见误区

1. close 返回后,TCP 状态必须立即消失

错误。

Java Socket 已关闭,不代表内核不再需要维护关闭协议状态。

2. 只有客户端进入 TIME_WAIT

错误。

谁主动关闭,谁通常进入 TIME_WAIT;服务端也完全可能成为主动关闭方。

3. 被动关闭方永远不会进入 TIME_WAIT

不严谨。

双方同时关闭时,两端都可能进入 TIME_WAIT。

4. TIME_WAIT 独占整个端口

错误。

它主要约束连接四元组及相关绑定、复用规则,不会让服务端监听端口对所有客户端完全失效。

5. TIME_WAIT 仍占用应用文件描述符

通常错误。

应用 fd 已关闭,但内核仍保存轻量的 TIME-WAIT 协议状态。

6. 65535 是服务器最大连接数

错误。

连接由四元组区分,服务端同一个监听端口可以对应大量不同远端连接。

7. tcp_fin_timeout 可以缩短 TIME_WAIT

错误。

它主要控制孤儿连接在 FIN_WAIT_2 状态中的等待时间。

8. tcp_tw_reuse 打开就一定安全

错误。

它只能在协议判断安全时复用,并与内核版本、时间戳和网络路径等因素有关。内核文档也明确不建议盲目修改。

9. tcp_tw_recycle 是通用优化参数

错误。

这是旧时代配置,当前主线内核文档已经不再提供,不应继续复制旧调优模板。

10. SO_REUSEADDR 能消除 TIME_WAIT

错误。

它改变的是地址绑定规则,不会删除 TCP 关闭状态。

11. TIME_WAIT 越少越好

错误。

TIME_WAIT 承担可靠关闭和旧报文隔离责任。

目标应该是:

避免不必要的短连接
合理控制连接速率
正确配置系统容量

而不是让所有 TIME_WAIT 立即消失。

12. CLOSE_WAIT 等一会儿会自动消失

不一定。

CLOSE_WAIT 通常在等待本地应用调用 close()。如果应用泄漏 Socket,它可能长期存在。


总结

TCP 是全双工协议,两个方向需要分别关闭。

正常关闭过程中:

主动关闭方发送 FIN
    ↓
被动关闭方进入 CLOSE_WAIT
    ↓
被动关闭方发送 FIN
    ↓
主动关闭方发送最后 ACK
    ↓
主动关闭方进入 TIME_WAIT

TIME_WAIT 主要解决两个问题:

最后 ACK 丢失时
能够重新确认对端的 FIN

旧连接报文仍在网络中时
避免污染相同四元组的新连接

它保留的是内核中的 TCP 协议状态,而不是继续占用 Java Socket 对象或原应用文件描述符。

TIME_WAIT 也不是简单地“占死一个端口”。

一条连接由四元组区分:

源 IP
源端口
目标 IP
目标端口

不同远端连接可以共享同一个服务端监听端口。

真正容易出现端口耗尽的场景通常是:

同一源 IP
    ↓
高频连接同一目标
    ↓
大量短连接
    ↓
本地主动关闭
    ↓
临时端口大量进入 TIME_WAIT

处理顺序应该是:

优先使用长连接和连接池
    ↓
降低连接创建与关闭速率
    ↓
分析谁在主动关闭
    ↓
检查临时端口范围
    ↓
评估源 IP 和机器扩容
    ↓
最后再考虑内核参数

TIME_WAIT 和 CLOSE_WAIT 也必须分开:

TIME_WAIT
    内核在等待协议安全时间

CLOSE_WAIT
    内核在等待应用关闭连接

tcp_fin_timeout 控制的不是 TIME_WAIT。

SO_REUSEADDR 也不能消除 TIME_WAIT。

旧文章中的 tcp_tw_recycle 不应继续用于现代系统。

最后,可以用一句话概括 TIME_WAIT:

TIME_WAIT 不是一条忘记释放的连接,而是 TCP 在连接关闭后,为最后一次确认和旧报文隔离保留的安全边界。

0

评论区