在计算机网络中,传输控制协议(TCP,Transmission Control Protocol) 作为传输层的核心协议,其提供的面向连接、可靠的数据传输服务是无数网络应用的基石 。无论是浏览网页、发送邮件还是远程登录,背后都离不开TCP的默默工作。
TCP协议由互联网工程任务组(IETF,Internet Engineering Task Force) 维护,其核心细节定义在 RFC 793 文档中 。理解TCP的关键,就在于掌握其连接建立与释放的过程------即经典的三次握手 与四次挥手。本文将结合图文,深度剖析这两个过程及其背后的设计逻辑。
一、 初识TCP报文头部
在详解握手与挥手之前,我们需要先认识TCP报文头部中的几个关键字段,它们是通信的语言基础 。
| 字段 | 名称 | 作用 |
|---|---|---|
| seq | 序号 | 占32位。用于标识从TCP源端向目的端发送的字节流。发起方发送数据时会对每个字节进行编号。 |
| ack | 确认号 | 占32位。只有ACK标志位为1时,该字段才有效。它表示期望收到对方下一个报文段的第一个数据字节的序号。 |
| SYN | 同步标志 | 在连接建立时用来同步序号。当SYN=1而ACK=0时,表示这是一个连接请求报文段。 |
| ACK | 确认标志 | 当该位为1时,确认号字段才有效。 |
| FIN | 终止标志 | 用来释放一个连接。当FIN=1时,表示此报文段的发送方数据已发送完毕,并要求释放传输连接。 |
二、 TCP三次握手:建立可靠连接的基石
1. 什么是三次握手?
三次握手(Three-Way Handshake) 是指建立一个TCP连接时,需要客户端和服务器端总共发送3个报文段以确认连接的建立 。这个过程保证了双方都具备发送和接收数据的能力,并协商好初始序列号。
2. 握手指南:一步一步建立连接
假设客户端(Client)主动发起连接,服务器端(Server)被动接受连接 。
第一次握手:
客户端向服务器端发送一个特殊的TCP报文段。该报文段首部的同步标志SYN被设置为1 ,并随机生成一个初始序列号,假设为 seq = x(x为随机值)。此时,客户端进入 SYN_SENT 状态,等待服务器确认 。
第二次握手:
服务器端收到客户端的SYN报文后,如果同意建立连接,则会发送一个应答报文。该报文段将SYN标志和ACK标志都设置为1 。确认号字段填写 ack = x + 1(表示收到了序号为x的报文,期望下次收到x+1),并为自己随机生成一个初始序列号 seq = y。此时,服务器端从LISTEN状态进入 SYN_RCVD 状态 。
第三次握手:
客户端收到服务器端的SYN+ACK报文后,需要再发送一个确认报文。该报文段将ACK标志设置为1 ,确认号 ack = y + 1,自己的序列号 seq = x + 1。这个报文段发送完毕后,客户端进入 ESTABLISHED 状态。当服务器端接收到这个ACK报文后,也进入 ESTABLISHED 状态。至此,TCP连接正式建立,双方可以开始传输数据 。
示意图:
客户端 服务器端
| SYN (seq=x) |
|------------------------------->| 第一次握手 (SYN_SENT -> SYN_RCVD)
| |
| SYN+ACK (seq=y, ack=x+1) |
|<-------------------------------| 第二次握手 (SYN_RCVD)
| |
| ACK (seq=x+1, ack=y+1) |
|------------------------------->| 第三次握手 (进入ESTABLISHED)
| |
| <------ 数据传输开始 ------> |
3. 为什么要三次?两次不行吗?
这是一个经典的面试题。三次握手的最主要目的是保证双方都有可靠的发送和接收能力 ,并防止失效的连接请求报文段突然又传送到服务器端而产生错误 。
-
第一次握手后:服务器端确认了客户端发送正常,自己接收正常。
-
第二次握手后:客户端确认了自己发送、接收正常,对方发送、接收正常。但服务器端此时还不知道自己发送是否正常、客户端接收是否正常。
-
第三次握手后:服务器端终于确认了自己发送正常、客户端接收正常。
如果只有两次握手 :假设客户端发送的第一个连接请求报文段在网络中某个节点长时间滞留了,导致客户端超时重传了第二个请求并成功建立连接,然后释放连接。此时,第一个滞留的报文段才到达服务器。如果是两次握手,服务器端收到这个失效的SYN报文后,会误以为是一个新的连接请求,于是发送ACK并进入ESTABLISHED状态,但客户端早已不管这个请求,导致服务器端白白分配资源在等待一个不存在的连接,造成了资源浪费 。因此,三次握手是防止历史重复连接初始化错误的最小次数 。
三、 TCP四次挥手:优雅地断开连接
1. 什么是四次挥手?
四次挥手(Four-Way Wavehand) 是指终止一个TCP连接时,需要客户端和服务器端总共发送4个报文段以确认连接的断开 。由于TCP连接是全双工的(即数据在两个方向上能同时传输),因此每个方向都必须单独进行关闭 。
2. 挥手流程:好聚好散
假设客户端主动发起关闭连接请求,服务器端被动关闭 。
第一次挥手:
客户端发送一个FIN标志位设置为1 的报文段,用来关闭客户端到服务器的数据传输。客户端序列号假设为 seq = u(u等于之前已传输数据的最后一个字节的序号加1)。发送后,客户端进入 FIN_WAIT_1 状态 。
第二次挥手:
服务器端收到FIN报文后,知道客户端数据已发送完毕,于是发送一个ACK标志位设置为1 的报文段作为应答。确认号 ack = u + 1,自己的序列号为 seq = v。此时,服务器端进入 CLOSE_WAIT 状态。
注意: 服务器端此时可能还有数据要发送给客户端,所以这只是一个确认,并不会马上关闭连接。客户端收到这个ACK后,就会进入 FIN_WAIT_2 状态,继续等待服务器端的FIN报文 。
第三次挥手:
当服务器端的数据也全部发送完毕后,服务器端向客户端发送一个FIN标志位设置为1 的报文段,用来关闭服务器到客户端的数据传输。确认号仍然是 ack = u + 1,序列号假设为 seq = w(可能又发送了一些数据,所以序号增加了)。此时,服务器端进入 LAST_ACK 状态 。
第四次挥手:
客户端收到FIN报文后,知道服务器端数据也发送完毕,于是发送最后一个ACK标志位设置为1 的报文段作为应答。确认号 ack = w + 1,自己的序列号为 seq = u + 1。发送完毕后,客户端并不立即关闭,而是进入 TIME_WAIT 状态。当服务器端收到这个ACK后,立即进入 CLOSED 状态。而客户端需要等待2MSL (最长报文段寿命)后,才会进入 CLOSED 状态 。
示意图:
客户端(主动关闭) 服务器端(被动关闭)
| FIN (seq=u) |
|------------------------------->| 第一次挥手 (FIN_WAIT_1)
| |
| ACK (seq=v, ack=u+1) |
|<-------------------------------| 第二次挥手 (CLOSE_WAIT)
| |
| FIN (seq=w, ack=u+1) |
|<-------------------------------| 第三次挥手 (LAST_ACK)
| |
| ACK (seq=u+1, ack=w+1) |
|------------------------------->| 第四次挥手 (进入TIME_WAIT)
| |
3. 为什么握手三次,挥手却要四次?
原因在于SYN和FIN的语义不同 ,以及TCP的全双工特性 。
-
三次握手时 :当服务器端收到客户端的SYN连接请求报文后,可以直接将SYN+ACK放在同一个报文段中发送回去。因为"同意连接"是一个即时动作,不需要做额外准备 。
-
四次挥手时 :当服务器端收到客户端的FIN关闭请求报文后,仅仅表示客户端不再发送数据了,但服务器端可能还有数据需要发送给客户端。因此,服务器不能立刻关闭连接,只能先回复一个ACK,告诉客户端"我知道你要关闭了,请等我处理完手头的数据"。直到服务器端的数据全部发送完毕,才能发送FIN报文。这就导致了ACK和FIN在大多数情况下是分开发送的,因此需要四个报文段 。
四、 深入探秘:状态变迁与TIME_WAIT
在握手和挥手的过程中,TCP实体内部经历了复杂的状态变迁。特别是TIME_WAIT状态,一直是面试和原理分析的重点。
1. 关键状态解读
-
LISTEN:服务器端等待客户端连接请求的状态 。
-
SYN_SENT:客户端发送SYN报文后,等待SYN+ACK报文的状态 。
-
SYN_RCVD:服务器端收到SYN,发送SYN+ACK后,等待ACK报文的状态 。
-
ESTABLISHED:连接已建立,进入正常数据传输状态 。
-
FIN_WAIT_1:主动关闭方发送FIN,等待ACK的状态 。
-
CLOSE_WAIT:被动关闭方收到FIN,发送ACK后,等待本地应用程序调用close()函数发送FIN的状态 。
-
FIN_WAIT_2:主动关闭方收到ACK,等待被动关闭方发送FIN的状态 。
-
LAST_ACK:被动关闭方发送FIN,等待最后一个ACK的状态 。
-
TIME_WAIT:主动关闭方收到FIN,发送最后一个ACK后,等待2MSL后进入CLOSED的状态 。
-
CLOSED:无连接状态 。
2. 为什么需要 TIME_WAIT 状态,且等待 2MSL?
TIME_WAIT状态是TCP协议设计中非常重要的一环,主要有两个原因 :
-
可靠地实现TCP全双工连接的终止 :
如果客户端发送的最后一次ACK报文在网络中丢失了,那么服务器端在LAST_ACK状态下会因为超时而重发FIN报文。如果没有TIME_WAIT状态,客户端已经关闭了,当它收到服务器重发的FIN时,就会响应一个RST报文(重置连接),导致服务器端进入错误状态。TIME_WAIT状态允许客户端在2MSL时间内再次收到这个FIN,并重发最后的ACK,确保服务器能正常关闭 。
-
允许老的重复分节在网络中消逝 :
假设没有TIME_WAIT状态,一个新的TCP连接被立即建立起来,且使用了与刚才完全相同的IP地址和端口。那么,上一轮连接中延迟到达的数据报可能会被误认为是新连接的报文,从而干扰新连接的数据传输。等待2MSL时间,足以让本次连接的所有报文(包括延迟的报文)从网络中消失,确保下一个新的连接不会受到旧连接数据的影响 。
MSL(Maximum Segment Lifetime) 是任何IP数据报能够在因特网中存活的最长时间。2MSL 就是发送和回复的最大往返时间 。
五、 特殊情况与安全性
1. 同时打开与同时关闭
虽然不常见,但理论上存在通信双方同时主动打开 或同时主动关闭的情况。
-
同时打开:双方同时发送SYN。此时会建立一条连接,但需要交换4个报文段,且两端都会从SYN_SENT状态变为SYN_RCVD状态,最后进入ESTABLISHED 。
-
同时关闭:双方同时发送FIN。此时双方都会进入FIN_WAIT_1状态,收到对方的FIN后发送ACK,并进入CLOSING状态,最后进入TIME_WAIT状态 。
2. 常见攻击:SYN Flood
这是一种典型的拒绝服务攻击(DDoS)。攻击者伪造大量不同的IP地址,向服务器快速发送SYN报文(第一次握手)。服务器每收到一个SYN,都会为这个半连接分配资源,并回复SYN+ACK(第二次握手),然后进入SYN_RCVD状态,等待客户端的第三次握手ACK。由于客户端是伪造的,永远不会回复ACK。这会导致服务器的半连接队列被迅速占满,无法再响应正常的连接请求 。
结语
TCP的三次握手和四次挥手不仅是协议规范的要求,更是其在不可靠的网络环境中实现可靠传输的智慧体现。通过三次握手,连接双方确认了彼此的存在与能力,协商好了起始状态;通过四次挥手和严谨的TIME_WAIT状态,保证了连接关闭的完整性和后续连接的"洁净"。
理解这些底层原理,对于网络编程、性能优化以及排查网络故障都具有非常重要的指导意义。