TCP(Transmission Control Protocol)是互联网中最核心的传输层协议之一,它提供了可靠的、面向连接的字节流服务。在 TCP 通信开始之前,通信双方需要通过「三次握手」建立连接。这个看似简单的过程,背后隐藏着精妙的设计考量。
一、TCP 连接的建立过程
1.1 三次握手的完整流程
三次握手的过程:
- 第一次握手(SYN):客户端发送一个 SYN 包(同步序列号),请求建立连接
- 第二次握手(SYN + ACK):服务端收到 SYN 后,返回 SYN+ACK 包,表示同意建立连接
- 第三次握手(ACK):客户端再发送一个 ACK 包,确认收到服务端的确认
1.2 抓包验证
使用 Wireshark 或 tcpdump 可以观察到实际的三次握手过程:
# 使用 tcpdump 抓取 TCP 握手包tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack|tcp-fin) != 0'
# 观察到的三次握手# 1. Client -> Server: SYN Seq=0# 2. Server -> Client: SYN ACK Seq=0 Ack=1# 3. Client -> Server: ACK Seq=1 Ack=1二、为什么不能两次握手?
这是理解 TCP 设计的核心问题。直觉上,似乎确认和应答机制用两次就够了,为什么要画蛇添足地多一次?
2.1 两次握手无法保证可靠性
分析一个具体场景:
问题的本质:TCP 是全双工通信,两次握手只能让一端确认另一端的存在,但无法让双方互相确认对方都「能够正常收发」。
2.2 历史请求的困扰
考虑这个场景:
- 客户端发送了一个 SYN 请求(seq=100),这个包在网络中迷路了
- 客户端超时重传了新的 SYN 请求(seq=200)
- 旧的 SYN(seq=100)最终到达服务端
- 如果只有两次握手,服务端会认为这是一个有效的连接请求并建立连接
- 而客户端并不知道这个「幽灵连接」的存在
2.3 两次握手无法同步初始序列号
TCP 的可靠传输依赖于序列号(Sequence Number)。每个字节的数据都有一个序列号,用于:
- 可靠性:检测丢失的数据包
- 有序性:保证数据按顺序到达
- 双工通信:全双工意味着双方都要有序列号
三次握手中的第二次握手,服务端要将自己的初始序列号(Initial Sequence Number,ISN 告知客户端。第三次握手时,客户端也要将自己的 ISN 告知服务端。只有这样,双方才能正确地:
- 对发送的数据进行编号
- 对接收的数据进行确认
- 处理乱序到达的数据包
三、为什么不需要四次握手?
既然三次握手都各有其道理,为什么不是四次、五次?能否更少?
3.1 四次握手是否必要?
四次握手在理论上是可行的,但效率低下。关键在于:服务端的 SYN 和 ACK 可以合并成一次传输。
3.2 合并的艺术
在 TCP 的状态转换中,服务端收到客户端的 SYN 后,可以立即发送 SYN+ACK(其中 ACK 是对客户端 SYN 的确认,SYN 是服务端自己的初始化序列号)。这两次信息可以同时发送,因此两次交互变成了三次。
核心洞察:服务端对客户端 SYN 的「确认」和 服务端自己的「SYN」在时间上紧密相关,合并发送是自然的优化。
四、三次握手的深层设计哲学
4.1 最小化连接建立时间
网络通信中,延迟是敌人。三次握手是最小化的、能够建立可靠全双工连接的交互次数:
- 一次交互:只能单向通信(类似 UDP)
- 两次交互:无法同步双方的序列号
- 三次交互:刚好满足所有需求
- 四次及以上:浪费,不需要
4.2 状态机的精妙设计
TCP 的连接管理通过状态机来实现:
三次握手完美对应了状态机的转换:
- 客户端发送 SYN → 客户端进入 SYN_SENT,服务端进入 SYN_RCVD
- 服务端发送 SYN+ACK → 双方都看到了对方的「我准备好了」
- 客户端发送 ACK → 双方都确认了对方知道自己准备好了
4.3 防御性的设计
TCP 的设计考虑到了网络的不可靠性:
| 设计要素 | 作用 |
|---|---|
| 序列号随机化 | 防止攻击者猜测序列号 |
| 超时重传 | 处理丢包 |
| 最大报文段寿命(MSS) | 防止迷途数据包永久存活 |
| 3-way handshake | 确保双方都能收发 |
五、半关闭与四次挥手
既然提到了三次握手,就不得不提 TCP 连接的关闭——四次挥手(Four-way Wave)。
5.1 为什么关闭需要四次?
关闭连接时,需要确保双方都没有数据要发送了。TCP 是全双工的,每个方向都必须单独关闭:
- 客户端发送 FIN,表示「我这边不会发送新数据了」
- 服务端收到 FIN,回复 ACK,表示「我收到了」
- 但此时服务端可能还有数据要发送给客户端,所以不能同时发送自己的 FIN
- 服务端发送完数据后,才发送自己的 FIN
- 客户端收到后回复 ACK
5.2 半关闭状态
TCP 允许连接的一端在接收完数据后单独关闭发送功能,这称为「半关闭」:
使用 shutdown() 函数可以实现半关闭,而 close() 会同时关闭读和写。
六、性能与安全的权衡
6.1 SYN Flood 攻击
三次握手设计的一个副作用是:服务端在收到客户端的 SYN 后,会分配资源并进入 SYN_RCVD 状态,等待客户端的 ACK。
攻击者发送大量 SYN 包但不完成握手,就会耗尽服务端的连接资源:
# SYN Flood 示意for i in $(seq 1 100000); do curl -S http://target.com &done防御手段包括:
- SYN Cookie:不存储半开连接,而是将信息编码在序列号中
- SYN Proxy:使用代理来验证 SYN 的真实性
- 减少 SYN_RCVD 超时时间
6.2 快速打开(TCP Fast Open)
为了减少连接建立的开销,TCP Fast Open(TFO)允许在第一次握手时就交换数据:
但这需要在之前已经建立过连接并获得过 Cookie,安全性需要仔细考虑。
七、总结
TCP 三次握手的设计完美地平衡了多个目标:
| 目标 | 三次握手如何实现 |
|---|---|
| 可靠性 | 通过序列号确认数据接收 |
| 全双工 | 双方都能发送和接收 |
| 效率 | 最小化交互次数 |
| 灵活性 | 支持半关闭等高级特性 |
| 安全性 | 序列号随机化防御攻击 |
理解三次握手的设计原理,不仅有助于网络编程,更是理解分布式系统可靠性的起点。在设计高并发系统时,这些原理依然有重要的指导意义。
参考引用
- RFC 793: Transmission Control Protocol — TCP 协议标准
- RFC 7414: A Roadmap for TCP Specification Documents — TCP 规范文档路线图
- TCP 的那些事儿(上) — 详解 TCP 三次握手四次挥手
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






