TCP协议在OSI七层模型 和TCP/IP四层模型 中都属于传输层。
TCP 三次握手(建立连接)
目标 : 在客户端和服务器之间建立一个全双工的、可靠的 TCP 连接。所谓"全双工",意味着双方都可以同时向对方发送和接收数据。
核心概念:
-
SYN (Synchronize Sequence Numbers): 同步序列号。用于发起一个新连接,并协商初始序列号。
-
ACK (Acknowledgment): 确认。用于确认收到数据包,其值表示"期望收到的下一个字节的序列号"。
-
seq (Sequence Number): 序列号。TCP 为每个字节分配一个编号,用于保证数据的有序性和可靠性。
-
ack (Acknowledgment Number): 确认号。表示接收方期望从发送方收到的下一个字节的序列号,即 ack = 收到的seq + 数据长度 + 1(如果携带数据)。在握手阶段,数据长度为0,所以 ack = 收到的seq + 1。
握手过程
假设客户端(Client)想要连接服务器(Server)。
第一次握手 (SYN)
-
发起方: 客户端
-
动作: 客户端向服务器发送一个 TCP 数据包。
-
标志位: SYN = 1
-
序列号: seq = x (x 是客户端随机生成的初始序列号 - ISN)
-
含义: "你好服务器,我想和你建立连接。我的初始序列号是 x。"
-
此时客户端状态: 进入 SYN-SENT 状态。
第二次握手 (SYN + ACK)
-
发起方: 服务器
-
动作: 服务器收到客户端的 SYN 包后,如果同意连接,则会回复一个数据包。
-
标志位: SYN = 1, ACK = 1
-
序列号: seq = y (y 是服务器随机生成的初始序列号 - ISN)
-
确认号: ack = x + 1
-
含义: "你好客户端,我收到了你的连接请求(ACK)。我同意建立连接,我的初始序列号是 y。"
-
此时服务器状态: 进入 SYN-RECEIVED 状态。
第三次握手 (ACK)
-
发起方: 客户端
-
动作: 客户端收到服务器的 SYN-ACK 包后,会再发送一个确认包。
-
标志位: ACK = 1
-
序列号: seq = x + 1 (因为第一次握手的 SYN 消耗了一个序列号)
-
确认号: ack = y + 1
-
含义: "好的服务器,我收到了你的确认。连接已经建立,我们可以开始传输数据了!"
-
此时双方状态: 客户端和服务器都进入 ESTABLISHED 状态,可以开始数据传输。
为什么是三次,而不是两次?
这是一个经典问题,核心是为了防止已失效的连接请求报文突然又传送到服务器,从而产生错误。
情景模拟:
-
客户端发送了一个 SYN 包(序列号=x)请求连接,但这个包在网络中滞留了(由于网络拥堵)。
-
客户端超时未收到响应,于是重发了一个新的 SYN 包(序列号=y)并成功建立连接,传输数据后关闭了连接。
-
此时,那个滞留在网络中的旧 SYN 包(序列号=x)终于到达了服务器。
-
如果是两次握手 : 服务器收到这个旧 SYN 包,会误以为客户端又发起了新连接,于是回复 SYN-ACK 并进入连接就绪状态。服务器会一直等待客户端发送数据,但客户端早已关闭,根本不会理会这个连接。这就导致了服务器的资源被白白浪费。
-
三次握手如何解决: 在三次握手的机制下,服务器回复 SYN-ACK 后,必须收到客户端的第三次 ACK 确认才会真正建立连接。在步骤3中,服务器发送了 SYN-ACK(ack=x+1),但客户端知道自己的当前序列号应该是 y 或更大,而不是 x,所以客户端会回复一个 RST 包来重置这个无效的连接,从而避免了服务器的资源浪费。
总结:三次握手确保了双方都确认了自己和对方的发送、接收能力是正常的,并且防止了失效的请求报文造成连接的误建立。
TCP 四次挥手(断开连接)
目标: 安全、可靠地关闭一个 TCP 连接。由于 TCP 连接是全双工的,每个方向都必须单独进行关闭。
核心概念 :
FIN (Finish) : 结束标志。表示发送方数据已发送完毕,要求释放连接。
挥手过程
假设客户端(Client)主动要求关闭连接。
第一次挥手 (FIN)
-
发起方: 客户端
-
动作: 客户端向服务器发送一个 TCP 数据包。
-
标志位: FIN = 1
-
序列号: seq = u (u 等于客户端已经传送过来的最后一个字节的序列号加1)
-
含义: "你好服务器,我数据发完了,想要关闭从我这到你那边的连接(即客户端不再发送数据)。"
-
此时客户端状态: 进入 FIN-WAIT-1 状态。
第二次挥手 (ACK)
-
发起方: 服务器
-
动作: 服务器收到 FIN 包后,立即发送一个确认包。
-
标志位: ACK = 1
-
序列号: seq = v
-
确认号: ack = u + 1
-
含义: "好的客户端,我收到你的关闭请求了。"
-
此时状态:
-
客户端收到这个 ACK 后,进入 FIN-WAIT-2 状态。此时,从客户端到服务器的连接已经关闭,客户端不能再发送数据,但还可以接收数据。
-
服务器进入 CLOSE-WAIT 状态。这个状态表示服务器可能还有数据需要发送给客户端。
-
第三次挥手 (FIN)
-
发起方: 服务器
-
动作: 当服务器也把所有剩余数据发送完毕后,它会发送一个 FIN 包。
-
标志位: FIN = 1, ACK = 1 (通常会和ACK一起发送)
-
序列号: seq = w (服务器在 CLOSE-WAIT 期间可能又发送了一些数据)
-
确认号: ack = u + 1 (这里依然是确认客户端的第一个FIN)
-
含义: "你好客户端,我这边数据也发完了,我也要关闭连接了(即服务器不再发送数据)。"
-
此时服务器状态: 进入 LAST-ACK 状态。
第四次挥手 (ACK)
-
发起方: 客户端
-
动作: 客户端收到服务器的 FIN 包后,发送一个确认包。
-
标志位: ACK = 1
-
序列号: seq = u + 1 (因为第一次挥手的 FIN 消耗了一个序列号)
-
确认号: ack = w + 1
-
含义: "好的服务器,我收到你的关闭请求了。"
-
此时状态:
-
客户端发送完 ACK 后,进入 TIME-WAIT 状态,等待 2MSL (Maximum Segment Lifetime,报文最大生存时间,通常为 2 分钟) 后,连接彻底关闭,进入 CLOSED 状态。
-
服务器收到这个 ACK 后,连接立即关闭,进入 CLOSED 状态。
-
为什么是四次挥手?
因为 TCP 是全双工的,关闭连接需要双方各自关闭自己方向的通道。当客户端发送 FIN 时,只表示它不再发送数据,但还可以接收数据。服务器收到 FIN 后,先回复一个 ACK,这完成了半关闭。然后,服务器需要等待自己所有数据都发送完毕后,再发送自己的 FIN,客户端再回复 ACK,这才完成整个连接的关闭。因此,需要两个独立的 FIN 和 ACK 过程。
通信模式
主要有三种模式:单工、半双工 和 全双工。
- 单工
-
核心概念 : 数据传输是单向的,就像一条单行道。一方固定为发送者,另一方固定为接收者,角色不能改变。
-
形象比喻:
- 广播电台(收音机): 电台只能发送信号,你的收音机只能接收信号,你无法通过收音机向电台发送声音。
-
关键词: 只发不收 或 只收不发。
- 半双工
-
核心概念 : 数据传输是双向 的,但不能同时进行。在同一时刻,只能有一方发送,另一方接收。它像一条单车道的桥,车流可以双向通行,但同一时间只能有一个方向的车辆通过。
-
形象比喻:
- 对讲机: 你按下通话键说话时,无法听到对方的声音;松开按键收听时,你才能听到对方说话。双方不能同时讲话。
-
关键词: 可以双向,但不能同时。
- 全双工
-
核心概念 : 数据传输是双向 的,并且可以同时进行。通信双方都可以在发送数据的同时接收数据。它像一条宽阔的双向车道,两个方向的车辆可以同时通行,互不干扰。
-
形象比喻:
- 电话通话: 你和朋友可以同时说话和听对方说话。
-
关键词: 双向且同时。

四次挥手 :因为连接是全双工的 ,有两个独立的数据通道,所以关闭它需要四个步骤:
-
客户端说:"我这边数据发完了(关闭客户端->服务器通道)。"
-
服务器说:"好的,我知道了。"(但此时服务器->客户端通道可能还在传输数据)
-
服务器数据发完后说:"我这边也发完了(关闭服务器->客户端通道)。"
-
客户端说:"好的,再见。"
如果TCP是半双工的,那么断开连接可能只需要两次挥手就够了,因为同一时间只有一方能通信。
为什么客户端在 TIME-WAIT 状态必须等待 2MSL?
这是挥手过程中最复杂也最关键的一个设计,主要有两个目的:
-
可靠地终止连接: 确保客户端的最后一个 ACK 能够到达服务器。如果这个 ACK 在网络中丢失,服务器在超时后会重发它的 FIN 包。客户端在 TIME-WAIT 状态下收到重发的 FIN,会重发 ACK 并重置 2MSL 计时器。如果没有 TIME-WAIT,客户端直接关闭,服务器将永远收不到 ACK,会一直处于 LAST-ACK 状态,无法正常关闭。
-
让旧连接的报文在网络中消逝: 等待 2MSL 时间,可以确保本次连接产生的所有报文都已经从网络中消失,这样在建立下一个新的连接时,就不会收到旧的、失效的报文,从而避免数据混淆。
如果已经建立了连接,但客户端突然出现故障怎么办?
TCP 有一个保活机制 。服务器每收到一次客户端的数据,会重置一个保活计时器(通常设置为2小时)。如果计时器超时都未收到客户端任何数据,服务器会发送一个探测报文段,以后每隔75秒发送一次。若连续10个探测报文都没有响应,服务器就认为客户端出了故障,接着就关闭这个连接。