概述
TCP 是一种面向连接的协议,通过三次握手建立连接、四次挥手断开连接。TCP 状态机描述了连接从建立到断开的完整生命周期,理解状态机是掌握 TCP 协议的关键。
一、TCP 状态机全景图
1.1 状态转换流程描述
**连接建立阶段(三次握手):**
-
CLOSED → SYN_SENT:客户端主动打开,发送 SYN
-
SYN_SENT → ESTABLISHED:收到服务器的 SYN+ACK,发送 ACK
-
CLOSED → LISTEN:服务器被动打开,等待连接
-
LISTEN → SYN_RCVD:收到客户端的 SYN,发送 SYN+ACK
-
SYN_RCVD → ESTABLISHED:收到客户端的 ACK
**数据传输阶段:**
- ESTABLISHED:双向数据传输状态
**连接断开阶段(四次挥手):**
-
ESTABLISHED → FIN_WAIT_1:主动关闭方发送 FIN
-
FIN_WAIT_1 → FIN_WAIT_2:收到对方的 ACK
-
FIN_WAIT_2 → TIME_WAIT:收到对方的 FIN,发送 ACK
-
TIME_WAIT → CLOSED:等待 2MSL 超时后关闭
-
ESTABLISHED → CLOSE_WAIT:被动关闭方收到 FIN,发送 ACK
-
CLOSE_WAIT → LAST_ACK:发送 FIN
-
LAST_ACK → CLOSED:收到最终 ACK
1.2 状态转换表
| 当前状态 | 事件 | 下一个状态 | 动作 |
|---------|------|-----------|------|
| CLOSED | 主动打开 | SYN_SENT | 发送 SYN |
| CLOSED | 被动打开 | LISTEN | 等待连接 |
| LISTEN | 收到 SYN | SYN_RCVD | 发送 SYN+ACK |
| SYN_SENT | 收到 SYN | SYN_RCVD | 发送 SYN+ACK |
| SYN_SENT | 收到 SYN+ACK | ESTABLISHED | 发送 ACK |
| SYN_RCVD | 收到 ACK | ESTABLISHED | - |
| ESTABLISHED | 主动关闭 | FIN_WAIT_1 | 发送 FIN |
| ESTABLISHED | 被动关闭 | CLOSE_WAIT | 收到 FIN |
| FIN_WAIT_1 | 收到 FIN+ACK | FIN_WAIT_2 | - |
| FIN_WAIT_1 | 收到 ACK | FIN_WAIT_1 | 等待 FIN |
| FIN_WAIT_2 | 收到 FIN | TIME_WAIT | 发送 ACK |
| CLOSE_WAIT | 主动关闭 | LAST_ACK | 发送 FIN |
| LAST_ACK | 收到 ACK | CLOSED | - |
| TIME_WAIT | 2MSL 超时 | CLOSED | - |
二、各状态详解
2.1 LISTEN(监听状态)
**定义:** 服务器端等待客户端连接请求的状态。
**场景:**
```
服务器启动后,调用 listen() 函数:
socket() → bind() → listen()
此时服务器进入 LISTEN 状态,等待客户端的 SYN 包。
```
**典型操作:**
```bash
Linux 查看 LISTEN 状态的连接
netstat -anp | grep LISTEN
ss -tlnp
示例输出
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1234/nginx
```
2.2 SYN_SENT(同步已发送)
**定义:** 客户端主动发起连接,发送 SYN 包后的状态。
**场景:**
```
客户端调用 connect() 函数:
-
发送 SYN 包(包含初始序列号 ISN)
-
进入 SYN_SENT 状态
-
等待服务器的 SYN+ACK 响应
时间线:
T0: 客户端发送 SYN(SYN, seq=1000)
T1: 等待服务器响应...
```
**典型问题:**
-
如果长时间停留在 SYN_SENT,可能是:
-
服务器未启动或端口未开放
-
网络不通
-
防火墙阻止
2.3 SYN_RCVD(同步已接收)
**定义:** 服务器收到客户端 SYN 包,发送 SYN+ACK 后的状态。
**场景:**
```
服务器处理连接请求:
-
收到 SYN(SYN, seq=1000)
-
发送 SYN+ACK(SYN+ACK, seq=2000, ack=1001)
-
进入 SYN_RCVD 状态
-
等待客户端的 ACK 确认
三次握手的第二阶段完成。
```
**SYN 攻击防护:**
```
SYN_RCVD 状态是 SYN 洪水攻击的目标。
防护措施:
-
SYN Cookie
-
限制半开连接数
-
缩短超时时间
```
2.4 ESTABLISHED(已建立连接)
**定义:** 连接已完全建立,双方可以进行数据传输。
**场景:**
```
三次握手完成:
客户端:SYN → SYN+ACK → ACK(进入 ESTABLISHED)
服务器:SYN → SYN+ACK → ACK(进入 ESTABLISHED)
此时可以发送和接收数据:
send() / recv() 操作都在这个状态进行。
```
**数据传输示例:**
```
客户端发送数据:
SYN\] → \[SYN+ACK\] → \[ACK\] → \[DATA, seq=1001, len=100\] → \[ACK, ack=1101
服务器接收数据:
SYN\] → \[SYN+ACK\] → \[ACK\] → \[DATA, seq=1001\] → \[ACK
```
2.5 FIN_WAIT_1(终止等待1)
**定义:** 主动关闭方发送 FIN 包后的状态,等待对方的 FIN+ACK 或 ACK。
**场景:**
```
客户端决定关闭连接:
-
调用 close() 或 shutdown(SHUT_WR)
-
发送 FIN(FIN, seq=1101)
-
进入 FIN_WAIT_1 状态
-
等待服务器响应
```
**可能的转换:**
-
收到 FIN+ACK → FIN_WAIT_2
-
收到 ACK → 仍在 FIN_WAIT_1(等待 FIN)
2.6 FIN_WAIT_2(终止等待2)
**定义:** 主动关闭方收到对方的 ACK(但未收到 FIN)后的状态。
**场景:**
```
客户端流程:
-
发送 FIN
-
收到 ACK(服务器确认收到 FIN)
-
进入 FIN_WAIT_2 状态
-
等待服务器的 FIN 包
此时客户端不能发送数据,但可以接收数据。
```
**异常情况:**
-
如果服务器长时间不发送 FIN,客户端会一直停留在 FIN_WAIT_2
-
可以设置 `tcp_fin_timeout` 超时时间
2.7 CLOSE_WAIT(关闭等待)
**定义:** 被动关闭方收到对方 FIN 后的状态。
**场景:**
```
服务器收到客户端的 FIN:
-
收到 FIN(FIN, seq=1101)
-
发送 ACK(ACK, ack=1102)
-
进入 CLOSE_WAIT 状态
-
应用层决定是否关闭
此时服务器需要通知应用层:"对方要关闭连接了"
```
**关键操作:**
```
应用层收到 EOF 通知后,应该:
-
完成剩余数据发送
-
调用 close() 关闭连接
-
发送 FIN 给对方
```
2.8 LAST_ACK(最后确认)
**定义:** 被动关闭方发送 FIN 后的状态,等待最后的 ACK。
**场景:**
```
服务器关闭连接:
-
应用层调用 close()
-
发送 FIN(FIN, seq=2201)
-
进入 LAST_ACK 状态
-
等待客户端的 ACK
收到 ACK 后,连接完全关闭。
```
2.9 TIME_WAIT(时间等待)
**定义:** 主动关闭方收到对方 FIN 并发送 ACK 后的状态,等待 2MSL 时间。
**场景:**
```
客户端收到服务器的 FIN:
-
收到 FIN(FIN, seq=2201)
-
发送 ACK(ACK, ack=2202)
-
进入 TIME_WAIT 状态
-
等待 2MSL(两倍最大段生命周期)
2MSL 通常为 1-4 分钟(Linux 默认 60 秒)
```
**TIME_WAIT 的作用:**
```
- 确保最后的 ACK 能到达对方
-
如果 ACK 丢失,对方会重传 FIN
-
TIME_WAIT 期间可以重新发送 ACK
- 防止旧连接的分段干扰新连接
-
网络中可能存在延迟的数据包
-
等待足够时间让它们过期
```
**TIME_WAIT 过多问题:**
```bash
查看 TIME_WAIT 数量
netstat -s | grep TIME_WAIT
优化参数
sysctl -w net.ipv4.tcp_tw_reuse=1 # 复用 TIME_WAIT 端口
sysctl -w net.ipv4.tcp_tw_recycle=1 # 快速回收
sysctl -w net.ipv4.tcp_fin_timeout=30 # 缩短超时时间
```
三、状态转换实例分析
3.1 正常连接建立(三次握手)
```
客户端 服务器
│ │
│----- SYN(seq=1000) ----------→│ CLOSED → LISTEN → SYN_RCVD
│ │
│←--- SYN+ACK(seq=2000, │
│ ack=1001) ----------------│
│ │
│----- ACK(ack=2001) ----------→│ SYN_RCVD → ESTABLISHED
│ │
▼ ▼
SYN_SENT → ESTABLISHED LISTEN → SYN_RCVD → ESTABLISHED
状态转换:
客户端:CLOSED → SYN_SENT → ESTABLISHED
服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED
```
3.2 正常连接断开(四次挥手)
```
客户端(主动关闭) 服务器(被动关闭)
│ │
│----- FIN(seq=1500) ----------→│ ESTABLISHED → CLOSE_WAIT
│ │
│←--- ACK(ack=1501) -----------│
│ │
▼ │
FIN_WAIT_1 → FIN_WAIT_2 │
│ │
│←--- FIN(seq=2500) -----------│ CLOSE_WAIT → LAST_ACK
│ │
│----- ACK(ack=2501) ----------→│ LAST_ACK → CLOSED
│ │
▼
FIN_WAIT_2 → TIME_WAIT → CLOSED
状态转换:
客户端:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
服务器:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
```
3.3 同时关闭
```
客户端 服务器
│ │
│----- FIN(seq=1500) ----------→│
│←--- FIN(seq=2500) -----------│
│ │
│----- ACK(ack=2501) ----------→│
│←--- ACK(ack=1501) -----------│
│ │
▼ ▼
TIME_WAIT TIME_WAIT
双方同时进入 TIME_WAIT 状态。
```
四、状态异常排查
4.1 常见异常状态
| 异常状态 | 可能原因 | 排查方向 |
|---------|---------|---------|
| SYN_SENT 过多 | 连接无法建立 | 检查目标端口、防火墙、网络连通性 |
| SYN_RCVD 过多 | SYN 攻击或半开连接 | 检查 SYN Cookie、连接限制 |
| FIN_WAIT_2 过多 | 被动关闭方未发送 FIN | 检查应用层是否正确关闭连接 |
| CLOSE_WAIT 过多 | 应用层未及时 close() | 检查代码逻辑、资源泄漏 |
| TIME_WAIT 过多 | 短连接频繁建立关闭 | 优化参数、使用连接池 |
4.2 排查工具
**netstat / ss 命令:**
```bash
查看所有状态
netstat -anp | grep tcp
统计各状态数量
netstat -anp | grep tcp | awk '{print $6}' | sort | uniq -c
查看特定状态
ss -t4 state TIME-WAIT
ss -t4 state FIN-WAIT-1
```
**tcpdump 抓包分析:**
```bash
抓包分析三次握手
tcpdump -i eth0 port 80 -nn -vv
过滤特定状态的包
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0' -nn
```
4.3 典型问题案例
**案例1:CLOSE_WAIT 过多**
```
现象:服务器有大量 CLOSE_WAIT 状态连接
原因:应用层没有及时调用 close()
排查步骤:
-
确认进程:netstat -anp | grep CLOSE_WAIT | head -5
-
检查代码:是否有未关闭的 socket
-
查看日志:是否有异常退出
-
使用 lsof:lsof -p <pid> | grep TCP
```
**案例2:SYN_SENT 无法建立连接**
```
现象:客户端一直停留在 SYN_SENT
原因:服务器端口未开放或防火墙阻止
排查步骤:
-
ping 测试网络连通性
-
telnet 测试端口:telnet server_ip port
-
检查服务器防火墙:iptables -L
-
检查服务器服务状态:systemctl status service
```
**案例3:TIME_WAIT 过多影响新连接**
```
现象:无法建立新连接,提示端口耗尽
原因:短连接导致 TIME_WAIT 占满端口
解决方法:
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
使用长连接替代短连接
```
五、状态机编程实践
5.1 客户端状态机实现
```python
class TCPStateClient:
def init(self):
self.state = 'CLOSED'
def open(self):
if self.state == 'CLOSED':
self.state = 'SYN_SENT'
print("发送 SYN")
def on_syn_ack(self):
if self.state == 'SYN_SENT':
self.state = 'ESTABLISHED'
print("发送 ACK")
def close(self):
if self.state == 'ESTABLISHED':
self.state = 'FIN_WAIT_1'
print("发送 FIN")
def on_ack(self):
if self.state == 'FIN_WAIT_1':
self.state = 'FIN_WAIT_2'
def on_fin(self):
if self.state == 'FIN_WAIT_2':
self.state = 'TIME_WAIT'
print("发送 ACK")
def timeout(self):
if self.state == 'TIME_WAIT':
self.state = 'CLOSED'
```
5.2 服务器状态机实现
```python
class TCPStateServer:
def init(self):
self.state = 'CLOSED'
def listen(self):
if self.state == 'CLOSED':
self.state = 'LISTEN'
def on_syn(self):
if self.state == 'LISTEN':
self.state = 'SYN_RCVD'
print("发送 SYN+ACK")
def on_ack(self):
if self.state == 'SYN_RCVD':
self.state = 'ESTABLISHED'
def on_fin(self):
if self.state == 'ESTABLISHED':
self.state = 'CLOSE_WAIT'
print("发送 ACK")
def close(self):
if self.state == 'CLOSE_WAIT':
self.state = 'LAST_ACK'
print("发送 FIN")
def on_final_ack(self):
if self.state == 'LAST_ACK':
self.state = 'CLOSED'
```
六、总结
6.1 状态机核心要点
```
- 三次握手:CLOSED → SYN_SENT → ESTABLISHED(客户端)
CLOSED → LISTEN → SYN_RCVD → ESTABLISHED(服务器)
- 四次挥手:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED(主动)
ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED(被动)
-
TIME_WAIT 是关键状态,确保连接完全关闭
-
CLOSE_WAIT 通常是应用层问题
```
6.2 状态异常处理原则
```
-
监控各状态数量变化
-
建立基线,识别异常
-
使用工具定位根因
-
根据具体状态采取对应措施
```
理解 TCP 状态机是网络编程和系统运维的基础,掌握状态转换规律有助于快速定位网络问题。