19.2 说说 TCP 的三次握手?
TCP 的三次握手是 TCP 协议建立一个可靠连接时必须执行的标准化流程。
我的理解是,三次握手就像是打电话,核心目的有两个:第一,确保双方都有收和发的能力;第二,同步通话的“初始话题编号”,以便后续交流不出错。
这个过程由三个步骤组成,涉及到 SYN
和 ACK
两个关键标志位。
1. 握手流程的三个步骤
我们可以把这个过程想象成客户端(发起方)和服务器(接收方)之间的一次精确“对表”。
- 第一次握手:客户端 -> 服务器 (SYN)
- 动作:客户端向服务器发送一个
SYN
包(同步序列编号)。这个包里包含一个初始序列号seq=x
。 - 含义:“你好,我想和你建立连接,我的初始序列号是
x
,你听得到吗?” - 状态:发送后,客户端进入
SYN_SENT
(已发送同步)状态。
- 动作:客户端向服务器发送一个
- 第二次握手:服务器 -> 客户端 (SYN + ACK)
- 动作:服务器收到
SYN
包后,会回复一个SYN+ACK
包。这个包里包含两个重要信息:- 一个确认号
ack=x+1
,表示“我收到了你编号为x
的包”。 - 服务器自己的初始序列号
seq=y
。
- 一个确认号
- 含义:“我听到了!你的
x
号消息我收到了。我也准备好和你连接了,我的初始序列号是y
。” - 状态:发送后,服务器进入
SYN_RCVD
(已接收同步)状态。
- 动作:服务器收到
- 第三次握手:客户端 -> 服务器 (ACK)
- 动作:客户端收到服务器的
SYN+ACK
包后,会发送最后一个ACK
包。这个包里包含一个确认号ack=y+1
,表示“我收到了你编号为y
的包”。 - 含义:“好的,我知道你准备好了。现在,连接正式建立,我们可以开始传输数据了。”
- 状态:发送后,客户端进入
ESTABLISHED
(已建立连接)状态。服务器收到这个ACK
包后,也进入ESTABLISHED
状态。
- 动作:客户端收到服务器的
至此,一个可靠的、双向的 TCP 连接就建立完成了。
2. 为什么必须是三次握手?—— 解决两大核心问题
面试官通常会追问,为什么不是两次或四次?三次握手是保证可靠连接的最小且必要的步骤,它主要解决了两个关键问题:
第一个问题:防止“已失效的历史连接”
- 场景:想象一下,客户端第一次发送的
SYN
包因为网络拥堵,在网络中“迷路”了。客户端等了很久没收到回复,于是重传了一个新的SYN
包,并成功建立了连接。数据传输完毕后,连接被关闭。 - 如果只有两次握手:此时,那个“迷路”的老
SYN
包终于到达了服务器。服务器会误认为这是一个全新的连接请求,于是它会向客户端发送SYN+ACK
并建立连接,然后傻傻地等待客户端发数据。但客户端其实早就“下线”了,这导致服务器凭空浪费了资源去维持一个无用的连接。 - 三次握手如何解决:有了第三次握手,当服务器收到那个老
SYN
包并回复SYN+ACK
后,客户端会发现自己并没有发起过这个连接请求(状态对不上),于是它会发送一个RST
(重置)包,告诉服务器:“你搞错了,关掉这个连接吧。” 这样就避免了服务器资源的浪费。第三次握手,本质上是客户端对本次连接的最终确认。
第二个问题:同步双方的初始序列号 (ISN)
- TCP 是一个可靠的协议,它需要保证数据包的有序、不重复、不丢失。这是通过序列号 (Sequence Number) 来实现的。
- 在连接建立时,双方必须协商并确认彼此的初始序列号。
- 第一次握手:客户端告诉服务器:“我的初始序列号是
x
。” - 第二次握手:服务器告诉客户端:“我收到了你的
x
(通过返回ack=x+1
),我的初始序列号是y
。” - 第三次握手:客户端告诉服务器:“我收到了你的
y
(通过返回ack=y+1
)。”
- 第一次握手:客户端告诉服务器:“我的初始序列号是
- 经过这三步,双方都明确知道了对方的起始序列号,后续的数据传输才能基于这个基准进行可靠的确认和重排。
3. 为什么不是两次或四次?
- 为什么不是两次:如上所述,两次握手无法防止历史连接的建立,也无法完成双方序列号的同步确认。
- 为什么不是四次:理论上可以四次(服务器先发
ACK
,再发SYN
),但没有必要。服务器的ACK
和SYN
完全可以在同一个数据包中发送,合并成第二次握手的SYN+ACK
,这样更高效。所以,三次是最优化的必要步骤。
总结:三次握手通过 SYN
和 ACK
的一来一回半,用最小的通信代价,解决了连接的有效性验证和初始序列号同步两大核心问题,为后续可靠的数据传输打下了坚实的基础。