TCP(Transmission Control Protocol,传输控制协议)是互联网协议栈中传输层的核心协议之一,它提供面向连接的、可靠的、基于字节流的通信。要理解TCP的工作原理,最重要的两个过程就是三次握手 (建立连接)和四次挥手(释放连接)。本文将结合经典的报文交换图,深入解析这两个过程,并解答常见疑问(如为什么是三次?为什么是四次?TIME_WAIT有什么用?)。
一、TCP基础概念回顾
在深入握手挥手之前,先了解几个关键字段:
-
序列号(seq):占4字节,表示本报文段所发送数据的第一个字节的序号。在建立连接时,双方会随机初始化一个序号(ISN)。
-
确认号(ack):占4字节,仅在ACK标志位为1时有效,表示期望收到对方下一个报文段数据的第一个字节的序号,通常为上次收到对方数据的最后一个序号加1。
-
标志位:
-
SYN:同步序号,用于建立连接。
-
ACK:确认,表示确认号字段有效。
-
FIN:结束,用于释放连接。
-
其他标志(RST、PSH、URG)本文不展开。
-
二、三次握手:建立可靠的连接
三次握手的主要目的是:确保双方都具备收发能力,并同步初始序列号。过程如下图所示(结合经典状态转移):
text
客户端 服务器
|-------- SYN=1, seq=x ---------->| (1) SYN-SENT
|<------ SYN=1, ACK=1, seq=y, ack=x+1 ----| (2) SYN-RCVD
|-------- ACK=1, seq=x+1, ack=y+1 ------->| (3) 进入ESTABLISHED
第一步:客户端发送SYN报文
客户端主动打开连接,发送一个SYN标志位置1的报文段,其中包含一个随机初始化的序列号 seq = x。此时客户端进入 SYN-SENT 状态。
第二步:服务器回复SYN+ACK
服务器收到SYN后,如果同意建立连接,则回复一个SYN和ACK标志位均为1的报文段:
-
确认号
ack = x + 1,表示期望收到客户端下一个字节序号为x+1的数据,同时确认了客户端的SYN。 -
服务器也随机初始化自己的序列号
seq = y。此时服务器进入 SYN-RCVD 状态。
第三步:客户端发送ACK确认
客户端收到服务器的SYN+ACK后,需要发送一个ACK报文段:
-
确认号
ack = y + 1,表示收到服务器的SYN。 -
序列号
seq = x + 1(因为之前发送的SYN不携带数据,所以序列号+1)。此时客户端进入 ESTABLISHED 状态。服务器收到这个ACK后也进入 ESTABLISHED 状态,连接建立完成。
为什么必须是三次握手?
-
防止已失效的连接请求突然到达服务器:如果客户端发出的第一个SYN因为网络延迟,在连接释放后才到达服务器,服务器误以为新请求并回复SYN+ACK。如果只有两次握手,服务器就会建立连接并等待数据,浪费资源。而三次握手时,客户端不会对服务器的SYN+ACK再发送确认,服务器收不到最后的ACK,就不会建立连接。
-
确保双方收发能力正常:第一次握手证明客户端的发送能力和服务器的接收能力;第二次握手证明服务器的发送能力和客户端的接收能力;第三次握手确认双方都已就绪。
-
同步初始序列号:双方需要交换初始序列号,以便后续数据的有序传输。
三、四次挥手:优雅地释放连接
TCP是全双工协议,即两个方向的数据传输可以独立关闭。因此释放连接时需要分别关闭每个方向,过程通常需要四次交互。假设客户端主动关闭连接:
text
主动方(客户端) 被动方(服务器)
|-------- FIN=1, seq=u -------->| (1) FIN-WAIT-1
|<------ ACK=1, seq=v, ack=u+1 -----| (2) CLOSE-WAIT
(此时进入FIN-WAIT-2)
|<------ FIN=1, ACK=1, seq=w, ack=u+1 ----| (3) LAST-ACK
|-------- ACK=1, seq=u+1, ack=w+1 ------>| (4) TIME-WAIT (2MSL后关闭)
第一步:主动方发送FIN
主动方(如客户端)发送一个FIN标志位置1的报文段,序列号为 seq = u(等于之前发送数据的最后一个字节的序号+1)。此时主动方进入 FIN-WAIT-1 状态,表示自己不再发送数据。
第二步:被动方回复ACK
被动方收到FIN后,发送一个ACK报文段:
-
确认号
ack = u + 1。 -
序列号
seq = v(等于自己发送数据的最后一个字节序号+1)。被动方进入 CLOSE-WAIT 状态。主动方收到ACK后进入 FIN-WAIT-2 状态,等待被动方关闭连接。此时被动方可能还有数据要发送,所以主动方仍需接收数据。
第三步:被动方发送FIN
被动方处理完剩余数据后,也发送一个FIN报文段(可携带ACK标志):
-
FIN=1, ACK=1。
-
序列号
seq = w(可能基于之前的v,但通常w=v,因为数据可能已经发送完毕)。 -
确认号仍为
ack = u+1。被动方进入 LAST-ACK 状态,等待最后的ACK。
第四步:主动方回复ACK
主动方收到FIN后,发送一个ACK报文段:
-
确认号
ack = w+1。 -
序列号
seq = u+1。此时主动方进入 TIME-WAIT 状态,等待2MSL(最大报文段生存时间)后进入 CLOSED 状态。被动方收到ACK后立即进入 CLOSED 状态。
为什么必须是四次挥手?
-
全双工关闭的独立性:当主动方发送FIN时,仅表示自己不再发送数据,但被动方可能还有数据未发送完,所以不能立即关闭。被动方先发送ACK表示收到关闭请求,待数据发送完毕后再发送自己的FIN。因此ACK和FIN通常分开发送,形成四次。
-
特殊情况:如果被动方在收到FIN后已经没有数据要发送,可以将ACK和FIN合并在一个报文段中发送,从而变成三次挥手(即第二、三步合并)。但标准实现仍建议分开发送,以确保兼容性和可靠性。
为什么需要TIME-WAIT状态?
TIME-WAIT状态持续2MSL(MSL为报文段最大生存时间,通常为30秒、1分钟或2分钟),主要作用:
-
确保最后的ACK能被对方收到:如果最后的ACK丢失,被动方会超时重发FIN,主动方在TIME-WAIT状态下可以重发ACK,保证连接可靠关闭。
-
防止旧报文干扰新连接:等待足够时间让所有属于旧连接的报文段在网络中消失,避免它们被后续使用相同端口的新连接错误接收。
四、常见问题与深入探讨
1. 三次握手可以携带数据吗?
第一次和第二次握手不能携带数据,因为此时连接尚未建立,携带数据会被对端丢弃(或导致安全隐患)。第三次握手可以携带数据,因为此时客户端已确认服务器有接收能力,且服务器一旦收到数据就会交给应用层。
2. 为什么初始序列号(ISN)要随机?
为了防止历史报文段被误认为是新连接的报文。如果ISN固定,攻击者可能伪造RST重置连接。随机化ISN(如RFC 1948中建议的算法)可提高安全性。
3. SYN Flood攻击是什么?如何防范?
攻击者伪造大量源IP发送SYN报文,但不完成第三次握手,导致服务器维护大量半连接(SYN-RCVD状态),耗尽资源。防范方法:
-
SYN Cookie:服务器不直接保存半连接信息,而是将连接参数加密后作为初始序列号(Cookie)在SYN+ACK中返回。收到ACK后验证Cookie,合法才分配资源。
-
增加半连接队列大小、缩短超时时间等。
4. TIME-WAIT为什么是2MSL?
MSL是报文段在网络中的最大生存时间。主动方发送ACK后,这个ACK可能丢失,被动方会重传FIN。主动方需要等待足够时间(至少1MSL)接收重传的FIN,并再发送ACK(又需要1MSL)。因此2MSL能保证:
-
最后一个ACK到达对端(如果丢失,对端重传的FIN到达主动方,主动方重置2MSL计时器并重发ACK)。
-
本连接的所有报文段从网络中消失。
5. 如果连接双方同时打开或同时关闭?
TCP设计允许同时打开(双方同时发送SYN)和同时关闭(双方同时发送FIN)。同时打开需要四次握手,但通过特殊处理可以建立连接;同时关闭需要四次挥手,但会进入类似FIN-WAIT的状态。实际应用中很少见,但TCP规范支持。
五、总结
TCP的三次握手和四次挥手是网络通信的基石。理解它们不仅有助于面试和考试,更能帮助我们在网络编程中排查问题(如连接建立缓慢、大量TIME-WAIT、半连接耗尽等)。本文结合经典的状态迁移和报文交换,从原理到常见问题做了详细解析。希望读者能通过抓包工具(如Wireshark)亲自观察这些过程,加深理解。
最后,如果你对网络协议感兴趣,推荐阅读《TCP/IP详解 卷1》和RFC 793,那里有更全面的描述。