前言
假设 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_reuse、tcp_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 在连接关闭后,为最后一次确认和旧报文隔离保留的安全边界。
评论区