TCP状态机与连接管理
约 1742 字大约 6 分钟
tcpstate-machine
2025-06-30
概述
TCP是面向连接的传输层协议,连接的建立、数据传输和断开都遵循严格的状态转换规则。TCP状态机共定义了11种状态,理解这些状态及其转换关系是排查网络问题的关键能力。
TCP的11种状态
| 状态 | 说明 |
|---|---|
| CLOSED | 初始状态,无连接 |
| LISTEN | 服务端等待连接请求 |
| SYN_SENT | 客户端已发送SYN,等待SYN+ACK |
| SYN_RCVD | 服务端收到SYN并发出SYN+ACK,等待ACK |
| ESTABLISHED | 连接已建立,可传输数据 |
| FIN_WAIT_1 | 主动关闭方已发送FIN,等待ACK |
| FIN_WAIT_2 | 主动关闭方收到FIN的ACK,等待对方FIN |
| CLOSING | 双方同时关闭 |
| TIME_WAIT | 等待2MSL后关闭 |
| CLOSE_WAIT | 被动关闭方收到FIN,等待应用层关闭 |
| LAST_ACK | 被动关闭方已发送FIN,等待最后ACK |
三次握手(Three-Way Handshake)
为什么是三次而不是两次?
两次握手无法防止历史重复连接的建立。如果客户端发出的旧SYN延迟到达服务端,服务端会认为是新的连接请求。三次握手允许客户端在收到SYN+ACK后验证这是否是当前期望的连接。
为什么不是四次?
三次已经足够让双方确认彼此的发送和接收能力。第三次ACK同时确认了服务端的发送能力和客户端的接收能力。
SYN Flood攻击与防御:
# 查看SYN_RCVD状态连接数
ss -n state syn-recv | wc -l
# 启用SYN Cookie防御
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
# 调整SYN队列大小
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
# 减少SYN+ACK重试次数
echo 2 > /proc/sys/net/ipv4/tcp_synack_retries四次挥手(Four-Way Teardown)
为什么需要四次挥手?
TCP是全双工的,每个方向的关闭独立进行。当一方发送FIN时,表示不再发送数据,但仍可接收。因此需要每个方向各一次FIN+ACK,共四次。
TIME_WAIT状态详解
TIME_WAIT是主动关闭方在发送最后一个ACK后进入的状态,需要等待2MSL(Maximum Segment Lifetime,通常60秒~2分钟)。
存在的原因:
- 确保最后的ACK到达对端: 如果最后的ACK丢失,对端会重发FIN,TIME_WAIT状态使得主动方可以重新发送ACK
- 让旧连接的延迟报文消逝: 防止旧连接的数据包被新连接误收
TIME_WAIT过多的问题与解决:
# 查看TIME_WAIT数量
ss -s
# 或
ss -n state time-wait | wc -l
# 允许TIME_WAIT端口重用(推荐)
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# 快速回收TIME_WAIT(不推荐,NAT环境下有问题)
# echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle # 已在Linux 4.12移除
# 减少FIN超时时间
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
# 增大本地端口范围
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range同时打开(Simultaneous Open)
当双方同时发起连接时,两端都经历 CLOSED → SYN_SENT → SYN_RCVD → ESTABLISHED。
同时打开最终只建立一条连接,而不是两条。
同时关闭(Simultaneous Close)
双方同时发送FIN,经历 ESTABLISHED → FIN_WAIT_1 → CLOSING → TIME_WAIT → CLOSED。
CLOSE_WAIT堆积问题
CLOSE_WAIT表示对端已关闭连接,但本端应用尚未调用close()。大量CLOSE_WAIT通常意味着程序Bug。
排查步骤:
# 查看CLOSE_WAIT连接及对应进程
ss -tnp state close-wait
# 输出示例:
# CLOSE-WAIT 1 0 192.168.1.10:8080 10.0.0.1:54321 users:(("java",pid=12345,fd=67))
# 查看进程打开的文件描述符
ls -la /proc/12345/fd | wc -l
# 使用lsof查看详细信息
lsof -p 12345 -i TCP常见原因:
- 忘记在finally块中关闭连接
- 连接池配置不当,未正确回收连接
- 读取操作阻塞,未设置超时
代码层面修复(Java示例):
// 错误写法:未关闭连接
Socket socket = new Socket("server", 8080);
InputStream is = socket.getInputStream();
// 处理数据...
// 忘记 socket.close() → 导致 CLOSE_WAIT
// 正确写法:try-with-resources
try (Socket socket = new Socket("server", 8080);
InputStream is = socket.getInputStream()) {
// 处理数据...
} // 自动关闭FIN_WAIT_2状态
如果主动关闭方一直停留在FIN_WAIT_2,说明对端的CLOSE_WAIT没有被处理(对端没有发送FIN)。Linux通过tcp_fin_timeout控制FIN_WAIT_2的超时:
# 查看当前设置
cat /proc/sys/net/ipv4/tcp_fin_timeout
# 默认60秒
# 调整(对orphan连接生效)
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout连接状态监控实战
# 统计各状态连接数
ss -s
# 按状态分组统计
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn
# 监控状态变化(每2秒刷新)
watch -n 2 "ss -ant | awk '{print \$1}' | sort | uniq -c | sort -rn"
# 查看特定IP的连接
ss -tn dst 10.0.0.1
# Prometheus + node_exporter 监控
# node_netstat_Tcp_CurrEstab → 当前ESTABLISHED数
# node_sockstat_TCP_tw → TIME_WAIT数关键内核参数总结
# /etc/sysctl.conf 优化示例
# 三次握手相关
net.ipv4.tcp_max_syn_backlog = 4096 # SYN队列长度
net.ipv4.tcp_syncookies = 1 # SYN Cookie防护
net.ipv4.tcp_syn_retries = 3 # SYN重试次数
net.ipv4.tcp_synack_retries = 2 # SYN+ACK重试次数
net.core.somaxconn = 4096 # accept队列长度
# TIME_WAIT相关
net.ipv4.tcp_tw_reuse = 1 # 重用TIME_WAIT连接
net.ipv4.tcp_fin_timeout = 30 # FIN_WAIT_2超时
net.ipv4.ip_local_port_range = 1024 65535 # 端口范围
# KeepAlive相关
net.ipv4.tcp_keepalive_time = 600 # 空闲600秒后探测
net.ipv4.tcp_keepalive_intvl = 15 # 探测间隔15秒
net.ipv4.tcp_keepalive_probes = 5 # 探测5次无响应则断开总结
TCP状态机是TCP协议的核心机制。在生产环境中,常见的问题包括:TIME_WAIT过多导致端口耗尽、CLOSE_WAIT堆积导致连接泄漏、SYN Flood攻击导致SYN_RCVD队列溢出。掌握状态转换流程和相关内核参数,能有效诊断和解决这些网络问题。
贡献者
更新日志
9f6c2-feat: organize wiki content and refresh site setup于