TCP 连接状态详解
TCP 连接状态由 RFC 793 定义,共 11 种标准状态。以下按连接生命周期详细解释:
1. 连接建立阶段
LISTEN (监听)
状态值 :0A(十六进制)/ 10(十进制)
含义:服务器端等待客户端的连接请求
触发条件 :调用 listen() 系统调用后
查看方法 :
bash
复制代码
ss -lnt # 查看所有监听端口
netstat -lnt
典型场景 :
bash
复制代码
# Web服务器监听
Proto Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp 0 0 *:80 *:* LISTEN
SYN-SENT (同步已发送)
状态值 :02 / 2
含义:客户端发送 SYN 后等待服务器回复
触发条件 :调用 connect() 后,发送 SYN 包
持续时间:通常很短(RTT + 处理时间)
问题诊断 :
bash
复制代码
# 大量SYN-SENT可能表示:
# 1. 服务器未响应
# 2. 防火墙阻止
ss -ant | grep -c SYN-SENT
SYN-RECEIVED (同步已接收)
状态值 :03 / 3
别名:SYN-RCVD
含义:服务器收到 SYN,发送 SYN+ACK 后等待客户端 ACK
触发条件:服务器收到 SYN,发送 SYN+ACK 后
半连接队列 :处于此状态的连接在半连接队列(SYN Queue)
安全注意 :SYN Flood 攻击的目标状态
调优参数 :
bash
复制代码
sysctl net.ipv4.tcp_max_syn_backlog # 半连接队列大小
sysctl net.ipv4.tcp_syncookies # 防御SYN Flood
2. 连接已建立阶段
ESTABLISHED (已建立连接)
状态值 :01 / 1
含义:连接已建立,可以正常传输数据
触发条件:完成三次握手后
全连接队列 :从 SYN-RCVD 移入全连接队列(Accept Queue)
查看详细 :
bash
复制代码
# 查看所有已建立连接
ss -nt
# 按进程查看
ss -ntp
数据传输 :
lua
复制代码
客户端 服务器
| ESTABLISHED |
| <-------------------------------> |
| 数据自由流动 |
3. 连接关闭阶段(正常关闭)
FIN-WAIT-1 (结束等待1)
状态值 :04 / 4
含义:主动关闭方发送 FIN 后,等待对方 ACK
触发条件 :调用 close() 或 shutdown(SHUT_WR)
可能转换 :
scss
复制代码
FIN-WAIT-1 → FIN-WAIT-2 # 收到ACK
FIN-WAIT-1 → CLOSING # 同时收到FIN(同时关闭)
FIN-WAIT-1 → TIME-WAIT # 收到FIN+ACK
FIN-WAIT-2 (结束等待2)
状态值 :05 / 5
含义:收到对方对 FIN 的 ACK,等待对方发送 FIN
持续时间:可保持较长时间(对方还未调用 close)
查看 :
bash
复制代码
# 查看处于此状态的连接
ss -ant | grep FIN-WAIT-2
问题 :过多连接可能导致资源耗尽
bash
复制代码
sysctl net.ipv4.tcp_fin_timeout # 默认60秒
CLOSE-WAIT (关闭等待)
状态值 :08 / 8
含义:被动关闭方收到 FIN 后,等待应用层关闭
触发条件:收到对方的 FIN,发送 ACK 后
关键问题 :应用层未及时关闭连接 会导致大量 CLOSE-WAIT
bash
复制代码
# 排查方法
ss -ant | grep CLOSE-WAIT
# 找到对应进程
ss -antp | grep CLOSE-WAIT
# 常见原因:未调用 close(),连接泄漏
CLOSING (正在关闭)
状态值 :0B / 11
含义 :双方同时尝试关闭连接(罕见)
触发条件 :
lua
复制代码
双方几乎同时发送FIN
客户端: FIN-WAIT-1 --发送FIN--> 服务器
服务器: FIN-WAIT-1 --发送FIN--> 客户端
双方收到对方FIN后进入CLOSING
查看 :
bash
复制代码
# 通常很少见
ss -ant | grep -c CLOSING
LAST-ACK (最后确认)
状态值 :09 / 9
含义:被动关闭方发送自己的 FIN 后,等待最后 ACK
触发条件:CLOSE-WAIT 状态的应用调用 close() 后
转换:收到 ACK 后进入 CLOSED
TIME-WAIT (时间等待)
状态值 :06 / 6
含义:主动关闭方收到对方的 FIN 并发送 ACK 后
持续时间 :2MSL (Maximum Segment Lifetime)
默认:60秒(Linux)
查看:sysctl net.ipv4.tcp_fin_timeout
目的 :
确保对方收到最后的 ACK
让旧连接的数据包在网络中消失
查看和管理 :
bash
复制代码
# 统计数量
ss -ant | grep -c TIME-WAIT
# 查看详细信息
ss -ant state time-wait
# 调优参数
sysctl net.ipv4.tcp_tw_reuse # 重用TIME-WAIT连接
sysctl net.ipv4.tcp_tw_recycle # 快速回收(谨慎使用)
sysctl net.ipv4.tcp_max_tw_buckets # 限制数量
4. 特殊/终止状态
CLOSED (关闭)
状态值 :07 / 7
含义:连接完全关闭,无资源占用
注意:统计中通常看不到,因为已释放资源
状态转换图详解
sql
复制代码
+---------+
| CLOSED |
+---------+
被动打开 | 主动打开
LISTEN | SYN-SENT
| | |
v | |
+---------+ | |
| LISTEN | | |
+---------+ | |
收到SYN | | |
发送SYN+ACK | | |
| | |
v | |
+-----------+ | |
|SYN-RECEIVED|<---|--------+
+-----------+ | 收到ACK
收到ACK | | |
| | |
v | |
+---------+ | |
|ESTABLISHED|<--+--------+
+---------+ 连接建立完成
| |
| 应用数据传输 |
| |
| 主动关闭 被动关闭
| 发送FIN 收到FIN
| 发送ACK
v |
+---------+ +-----------+
|FIN-WAIT-1| | CLOSE-WAIT|
+---------+ +-----------+
| |
收到ACK | |应用调用close
v |发送FIN
+---------+ +---------+
|FIN-WAIT-2| |LAST-ACK |
+---------+ +---------+
| |
收到FIN | |收到ACK
发送ACK | |
v v
+---------+ +---------+
|TIME-WAIT| | CLOSED |
+---------+ +---------+
| 等待2MSL
v
+---------+
| CLOSED |
+---------+
完整查看脚本
bash
复制代码
#!/bin/bash
# tcp_state_detailed.sh
# 查看详细的TCP状态统计
echo "================================================"
echo " TCP 连接状态详细统计"
echo "================================================"
echo ""
# 使用 ss 查看(推荐)
echo "【方法1: ss 命令】"
echo "状态 数量 描述"
echo "-----------------------"
ss -ant | awk 'NR>1 {print $2}' | sort | uniq -c | while read count state; do
case $state in
LISTEN) desc="监听端口,等待连接" ;;
SYN-SENT) desc="客户端已发送SYN" ;;
SYN-RECV) desc="服务器收到SYN,已回复SYN+ACK" ;;
ESTAB) desc="连接已建立,数据传输中" ;;
FIN-WAIT-1) desc="主动关闭方发送FIN,等待ACK" ;;
FIN-WAIT-2) desc="收到FIN的ACK,等待对方FIN" ;;
TIME-WAIT) desc="等待2MSL,确保连接完全关闭" ;;
CLOSED) desc="连接完全关闭" ;;
CLOSE-WAIT) desc="被动关闭方收到FIN,等待应用关闭" ;;
LAST-ACK) desc="被动关闭方发送FIN,等待最后ACK" ;;
CLOSING) desc="双方同时尝试关闭" ;;
UNKNOWN) desc="未知状态" ;;
*) desc="" ;;
esac
printf "%-12s %6d %s\n" "$state" "$count" "$desc"
done
echo ""
echo "【方法2: /proc/net/tcp 原始数据】"
echo "状态(十六进制) 数量"
echo "-------------------"
awk '{print $4}' /proc/net/tcp | awk -F: '{print $1}' | sort | uniq -c | sort -rn | while read count hex_state; do
case $hex_state in
0A) state="LISTEN" ;;
01) state="ESTABLISHED" ;;
02) state="SYN_SENT" ;;
03) state="SYN_RECV" ;;
04) state="FIN_WAIT1" ;;
05) state="FIN_WAIT2" ;;
06) state="TIME_WAIT" ;;
07) state="CLOSE" ;;
08) state="CLOSE_WAIT" ;;
09) state="LAST_ACK" ;;
0B) state="CLOSING" ;;
*) state="UNKNOWN($hex_state)" ;;
esac
printf "%-10s %6s %6d\n" "$hex_state" "$state" "$count"
done
echo ""
echo "【关键指标监控】"
total=$(ss -ant | grep -c -v State)
echo "总TCP连接数: $total"
echo ""
# 监控建议
echo "【健康检查建议】"
close_wait=$(ss -ant state close-wait | grep -c -v State)
if [ $close_wait -gt 100 ]; then
echo "⚠️ 警告: CLOSE-WAIT 过多($close_wait),可能存在连接泄漏"
echo " 排查方法:"
echo " 1. ss -antp state close-wait"
echo " 2. 检查对应应用的连接关闭逻辑"
fi
time_wait=$(ss -ant state time-wait | grep -c -v State)
if [ $time_wait -gt 10000 ]; then
echo "⚠️ 警告: TIME-WAIT 过多($time_wait),可能影响新连接"
echo " 优化建议:"
echo " 1. sysctl net.ipv4.tcp_tw_reuse=1"
echo " 2. sysctl net.ipv4.tcp_max_tw_buckets=2000000"
fi
状态诊断与调优
常见问题及解决
问题1:大量 TIME-WAIT
bash
复制代码
# 原因:短连接过多
# 解决:
sysctl -w net.ipv4.tcp_tw_reuse=1 # 重用
sysctl -w net.ipv4.tcp_tw_recycle=1 # 回收(NAT环境慎用)
sysctl -w net.ipv4.tcp_max_tw_buckets=2000000
问题2:大量 CLOSE-WAIT
bash
复制代码
# 原因:应用未正确关闭连接
# 排查:
lsof -nP -iTCP -sTCP:CLOSE_WAIT
# 或
ss -antp state close-wait
# 检查对应进程的代码
问题3:大量 SYN-RECV
bash
复制代码
# 原因:可能遭受SYN Flood攻击
# 防御:
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_syn_backlog=4096
sysctl -w net.ipv4.tcp_synack_retries=2
状态监控工具
实时监控脚本
bash
复制代码
#!/bin/bash
# 实时监控TCP状态变化
watch -n 1 '
echo "TCP状态实时监控 - $(date)"
echo "----------------------------------------"
ss -ant | awk '\''NR>1 {count[$2]++} END {
states["LISTEN"]="监听";
states["SYN-SENT"]="发SYN";
states["SYN-RECV"]="收SYN";
states["ESTAB"]="已连接";
states["FIN-WAIT-1"]="等FIN-ACK";
states["FIN-WAIT-2"]="等对方FIN";
states["TIME-WAIT"]="等2MSL";
states["CLOSE-WAIT"]="等应用关";
states["LAST-ACK"]="等最后ACK";
states["CLOSING"]="同时关闭";
for(s in count) {
printf "%-12s %6d %s\n", s, count[s], states[s]
}
}'\'' | sort
'