TCP 报文结构回顾
在分析握手和挥手之前,先回顾 TCP 头部的关键字段:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |C|E|U|A|P|R|S|F| |
| Hdr | Rsvd |W|C|R|C|S|S|Y|I| Window |
| Len | |R|E|G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
其中 SYN、ACK、FIN、RST 是控制位,决定了报文的类型。
三次握手详解
第一次握手:SYN
客户端 ──── SYN=1, seq=x ────> 服务器
- 客户端发送 SYN 报文,SYN 标志位置 1
- 序列号 seq=x(随机初始值,称为 ISN)
- 客户端进入 SYN_SENT 状态
第二次握手:SYN+ACK
客户端 <──── SYN=1, ACK=1, seq=y, ack=x+1 ──── 服务器
- 服务器收到 SYN,回复 SYN+ACK
- SYN=1 表示这是服务器的连接请求
- ACK=1 表示确认号有效
- seq=y(服务器的 ISN)
- ack=x+1 表示确认收到了客户端的 seq=x
- 服务器进入 SYN_RCVD 状态
第三次握手:ACK
客户端 ──── ACK=1, seq=x+1, ack=y+1 ────> 服务器
- 客户端发送 ACK 确认
- seq=x+1(接上一次的序列号)
- ack=y+1(确认服务器的 seq=y)
- 双方进入 ESTABLISHED 状态
为什么是三次?
核心原因:防止历史连接的初始化。
如果只有两次握手,服务器无法确认客户端是否收到了自己的 SYN+ACK。三次握手的本质是双方各确认一次收发能力:
| 握手 | 确认内容 |
|---|---|
| 第 1 次 | 服务器确认:客户端能发 |
| 第 2 次 | 客户端确认:服务器能收能发 |
| 第 3 次 | 服务器确认:客户端能收 |
三次握手同时也是最小的确认次数。
四次挥手详解
为什么挥手是四次?
TCP 是全双工协议,每个方向的关闭需要独立进行。发送 FIN 只表示"我不会再发数据了",但对方可能还有数据要发。
挥手过程
主动关闭方 被动关闭方
│ │
│──── FIN=1, seq=u ────────> │ ① 主动方发送 FIN
│ │
│ <──── ACK=1, ack=u+1 ─────│ ② 被动方确认 FIN
│ │
│ (被动方继续发送剩余数据) │ ③ 半关闭状态
│ │
│ <──── FIN=1, seq=w ────────│ ④ 被动方发送 FIN
│ │
│──── ACK=1, ack=w+1 ────────> │ ⑤ 主动方确认 FIN
│ │
│ TIME_WAIT (2MSL) │ ⑥ 等待确认到达
│ │
状态变迁
主动关闭方: ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
被动关闭方: ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
为什么需要 TIME_WAIT?
TIME_WAIT 持续 2MSL(Maximum Segment Lifetime),有两个作用:
- 确保最后的 ACK 能到达:如果最后的 ACK 丢失,被动方会重发 FIN,主动方需要在 TIME_WAIT 状态重新发送 ACK
- 防止旧连接的报文干扰新连接:等待足够时间让网络中残留的报文消亡
抓包实战
使用 tcpdump 抓取握手过程:
# 抓取 80 端口的 TCP 握手
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack|tcp-fin) != 0' -nn -c 20 port 80
# 更详细的抓包
tcpdump -i eth0 -nn -S port 80
使用 Wireshark 过滤器:
tcp.flags.syn == 1 && tcp.flags.ack == 0 # 过滤 SYN
tcp.flags.fin == 1 # 过滤 FIN
tcp.flags.rst == 1 # 过滤 RST
常见问题排查
1. SYN_RCVD 状态堆积
# 查看 SYN_RCVD 连接数
ss -ant | grep SYN-RCVD | wc -l
# 可能是 SYN Flood 攻击,启用 SYN Cookie
sysctl -w net.ipv4.tcp_syncookies=1
2. TIME_WAIT 过多
# 查看 TIME_WAIT 数量
ss -ant | grep TIME-WAIT | wc -l
# 启用 TIME_WAIT 复用
sysctl -w net.ipv4.tcp_tw_reuse=1
3. 连接超时排查
# 查看重传统计
netstat -s | grep -i retrans
# 查看超时参数
sysctl net.ipv4.tcp_syn_retries
sysctl net.ipv4.tcp_fin_timeout
总结
TCP 的握手和挥手是可靠的基石。理解报文级别的交互,对于排查网络问题、优化性能至关重要。