🌊 TCP三次握手与四次挥手全解析(含序列号动态追踪)
🔑 TCP 协议核心机制
- 序列号 (seq):数据字节流的唯一标识(32位循环计数器)
- 确认号 (ack):期望接收的下一个序列号(ack = 接收方seq + 1)
- 标志位:SYN(建立连接)、ACK(确认)、FIN(关闭连接)
🛠️ 三次握手详解(连接建立)
序列号变化追踪(假设初始序列号:Client=1000, Server=5000)
步骤 | 方向 | 标志位 | seq 值 | ack 值 | 关键说明 |
---|---|---|---|---|---|
1 | C → S | SYN=1 | 1000 | - | 客户端随机初始化seq |
2 | S → C | SYN=1,ACK=1 | 5000 | 1001 | 服务端ack=客户端seq+1 |
3 | C → S | ACK=1 | 1001 | 5001 | 客户端seq+1,ack=服务端seq+1 |
📌 SYN消耗序列号:发送SYN会使序列号+1(SYN标志位占据1字节序列空间)
🚪 四次挥手详解(连接释放)
序列号变化追踪(通信结束时:Client seq=8000, Server seq=12000)
步骤 | 方向 | 标志位 | seq 值 | ack 值 | 状态变化 |
---|---|---|---|---|---|
1 | C → S | FIN=1 | 8000 | - | Client: ESTAB→FIN_WAIT_1 |
2 | S → C | ACK=1 | 12000 | 8001 | Server: ESTAB→CLOSE_WAIT Client: FIN_WAIT_1→FIN_WAIT_2 |
3 | S → C | FIN=1,ACK=1 | 12001 | 8001 | Server: CLOSE_WAIT→LAST_ACK |
4 | C → S | ACK=1 | 8001 | 12002 | Client: FIN_WAIT_2→TIME_WAIT Server: CLOSED 等待2MSL后Client关闭 |
⚠️ 序列号递增规则:
- FIN标志位消耗1序列号(与SYN相同)
- 发送数据时:seq增加 = 数据字节数
- 发送控制位:seq增加 = 标志位数量
🔥 关键机制深度解析
1. 初始序列号 (ISN) 的随机性
- 生成算法:ISN = (计时器 × 加密因子) mod 2³²
(现代系统使用安全随机数生成) - 目的:防止前序连接的报文混淆(旧连接相同端口复用)
2. TIME_WAIT 状态(2MSL等待)
// Linux内核参数配置
net.ipv4.tcp_fin_timeout = 60 // 控制TIME_WAIT持续时间
net.ipv4.tcp_max_tw_buckets = 18000 // 最大TIME_WAIT连接数
- MSL定义:Max Segment Lifetime(报文最大生存时间,通常30秒)
- 2MSL = 60秒:保证网络中残余报文消亡
3. 半关闭状态(Half-Close)
当一方发送FIN后进入半关闭状态:
4. 序列号回绕处理
32位序列号在高速网络中可能溢出(10Gbps≈1.2GB/s):
// Linux内核序列号比较函数
static inline bool tcp_before_seq(u32 seq1, u32 seq2) {return (s32)(seq1 - seq2) < 0;
}
// 处理示例:
// seq1=0xFFFFFF00, seq2=0x00000010 → 判断seq1 < seq2(溢出场景)
⚠️ 高频面试问题
-
为什么是三次握手?
阻止历史重复连接初始化(防止旧SYN干扰新连接)
-
为什么需要TIME_WAIT状态?
确保被动关闭方能收到最终ACK(防止LAST_ACK超时重传FIN)
-
服务器如何应对大量TIME_WAIT?
# 内核优化命令 echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse # 重用TIME_WAIT连接 echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle # 快速回收(慎用)
-
握手时的序列号为什么随机?
避免TCP序列号预测攻击(如IP欺骗攻击)
-
为什么挥手是四次而不是三次?
TCP连接允许单向关闭(半关闭状态),需要独立确认双向关闭