一、TCP四次挥手的基本流程
1. 完整四次挥手过程
2. 状态转换详解
步骤 | 主动关闭方状态变化 | 被动关闭方状态变化 |
---|---|---|
1 | ESTABLISHED → FIN_WAIT_1 | ESTABLISHED → CLOSE_WAIT |
2 | FIN_WAIT_1 → FIN_WAIT_2 | CLOSE_WAIT (保持不变) |
3 | FIN_WAIT_2 (保持不变) | CLOSE_WAIT → LAST_ACK |
4 | FIN_WAIT_2 → TIME_WAIT | LAST_ACK → CLOSED |
二、四次挥手的深层解析
1. 第一次挥手:主动关闭方的FIN报文
核心字段:
-
FIN=1
:终止连接标志 -
seq=u
:当前序列号(u为已发送数据的最后字节序号+1)
内核实现细节:
// Linux内核实现 (net/ipv4/tcp.c)
void tcp_send_fin(struct sock *sk) {
struct sk_buff *skb = tcp_write_queue_tail(sk);
int mss = tcp_current_mss(sk);
// 分配新的SKB用于FIN
skb = skb_copy_expand(skb, skb_headroom(skb),
mss + MAX_HEADER,
GFP_ATOMIC);
// 设置FIN标志
TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_FIN;
TCP_SKB_CB(skb)->end_seq++;
// 更新序列号
tcp_advance_send_head(sk, skb);
tcp_event_new_data_sent(sk, skb);
// 添加到发送队列
__tcp_push_pending_frames(sk, mss, TCP_NAGLE_OFF);
}
为什么需要FIN标志?
FIN(Finish)标志表示发送方已完成数据发送,希望关闭连接。FIN占用一个序列号空间,这意味着:
-
接收方需要显式确认FIN包
-
FIN包可能重传,确保可靠性
-
FIN包可以携带数据(但通常不携带)
2. 第二次挥手:被动关闭方的ACK响应
核心字段:
-
ACK=1
:确认标志 -
ack=u+1
:确认序列号(确认FIN包)
底层处理流程:
-
接收FIN包,检查序列号有效性
-
通知应用层连接关闭(触发EOF)
-
发送ACK确认
-
进入CLOSE_WAIT状态,等待应用层关闭
关键问题:为什么不能立即发送FIN?
被动关闭方可能还有数据需要发送:
-
应用层可能还有待发送数据
-
需要完成事务处理
-
需要发送结束协议数据
3. 第三次挥手:被动关闭方的FIN报文
触发条件:
-
应用层调用close()或shutdown()
-
所有待发送数据已传输完成
-
发送缓冲区清空
内核处理逻辑:
// Linux内核处理应用层关闭 (net/ipv4/tcp.c)
int tcp_close(struct sock *sk, long timeout) {
// 检查发送队列是否为空
if (!skb_queue_empty(&sk->sk_write_queue)) {
// 设置延迟关闭标志
sk->sk_lingertime = timeout;
tcp_set_state(sk, TCP_FIN_WAIT1);
tcp_send_fin(sk);
} else {
// 立即发送FIN
tcp_send_fin(sk);
tcp_set_state(sk, TCP_LAST_ACK);
}
// ...
}
4. 第四次挥手:主动关闭方的最终ACK
关键特性:
-
ACK=1
:确认被动关闭方的FIN -
ack=w+1
:确认序列号(w为被动关闭方的最后序列号)
TIME_WAIT状态的深层意义:
2MSL等待的数学原理:
-
MSL(Maximum Segment Lifetime)通常为30秒或1分钟
-
2MSL = 2 × MSL(典型值为60秒或120秒)
-
确保网络中所有数据包"死亡"
-
允许重传最后的ACK