TCP 的三次握手(Three-Way Handshake)是建立 TCP 连接的核心过程,通过三次数据包交互,确保通信双方的发送和接收能力正常,并协商初始序列号(用于后续可靠传输)。这一机制是 TCP 实现“面向连接”和“可靠传输”的基础。
一、三次握手的本质:确认双方通信能力
TCP 是“面向连接”的协议,通信前必须先建立连接。三次握手的核心目的是:
- 让客户端和服务器互相确认对方的发送和接收能力正常(避免一方能发不能收,或能收不能发)。
- 协商初始序列号(ISN,Initial Sequence Number),为后续数据传输的可靠性(按序、去重、确认)奠定基础。
二、三次握手的详细过程
三次握手涉及三个关键数据包(均为 TCP 协议的控制报文,不携带业务数据),双方状态变化如下:
1. 第一次握手:客户端 → 服务器(发起连接请求)
-
客户端行为:
客户端(如浏览器、Java 客户端)创建Socket
时,向服务器发送 SYN 报文(同步报文段,SYN=1
),并生成自己的初始序列号seq = x
(随机生成的 32 位整数)。
客户端状态从CLOSED
变为SYN_SENT
(等待服务器确认)。 -
报文内容:
SYN=1, seq=x
2. 第二次握手:服务器 → 客户端(确认请求并发起同步)
-
服务器行为:
服务器(如 JavaServerSocket
)收到 SYN 报文后,确认“客户端能发,服务器能收”。
服务器回复 SYN + ACK 报文:SYN=1
:表示服务器也向客户端发起同步,生成自己的初始序列号seq = y
(随机生成)。ACK=1
:表示确认收到客户端的 SYN,确认号ack = x + 1
(期望下次收到客户端从x+1
开始的数据)。
服务器状态从LISTEN
变为SYN_RCVD
(等待客户端确认)。
-
报文内容:
SYN=1, ACK=1, seq=y, ack=x+1
3. 第三次握手:客户端 → 服务器(确认服务器同步)
-
客户端行为:
客户端收到 SYN + ACK 报文后,确认“服务器能收、能发”。
客户端回复 ACK 报文,确认号ack = y + 1
(期望下次收到服务器从y+1
开始的数据),序列号seq = x + 1
(遵循 TCP 序列号递增规则)。
客户端状态从SYN_SENT
变为ESTABLISHED
(连接建立,可传输数据)。 -
服务器行为:
服务器收到 ACK 报文后,确认“客户端能收”,状态从SYN_RCVD
变为ESTABLISHED
。 -
报文内容:
ACK=1, seq=x+1, ack=y+1
三、为什么需要三次握手?(核心原因)
三次握手是为了避免“无效连接请求”导致服务器资源浪费,同时确保双方通信能力完整。
-
若仅两次握手:
假设客户端发送的第一个 SYN 报文因网络延迟滞留,客户端超时后重新发送 SYN 并完成两次握手建立连接。通信结束后,滞留的 SYN 报文到达服务器,服务器会误认为是新的连接请求,再次建立连接并分配资源(如端口、缓冲区),但客户端实际不会使用该连接,导致服务器资源浪费。 -
三次握手的必要性:
第三次握手让服务器确认“客户端已收到自己的 SYN + ACK”,确保客户端是“活跃”的,从而避免为无效请求分配资源。同时,三次交互完整确认了双方的“发送-接收”能力:- 第一次:服务器确认“客户端能发,服务器能收”。
- 第二次:客户端确认“服务器能收、能发”。
- 第三次:服务器确认“客户端能收”。
四、三次握手的关键细节
-
初始序列号(ISN)的随机性:
ISN 不是固定的 0 或 1,而是随机生成的(通常随时间递增),目的是防止序列号猜测攻击(攻击者伪造数据包时难以猜对正确序列号)。 -
SYN 报文的消耗:
SYN 报文会消耗一个序列号(如客户端发送seq=x
后,下一个序列号是x+1
),而 ACK 报文不消耗序列号(仅确认对方的序列号)。 -
半连接队列:
服务器在第二次握手后(状态SYN_RCVD
),会将连接放入“半连接队列”,直到收到第三次握手的 ACK 后,才移至“全连接队列”(ESTABLISHED
状态)。若半连接队列满,服务器可能丢弃新的 SYN 请求(导致客户端连接失败)。
五、Java 中三次握手的体现(代码与现象)
Java 中通过 Socket
和 ServerSocket
实现 TCP 连接,三次握手由底层操作系统自动完成,开发者无需手动处理,但可通过代码观察连接建立过程:
示例:Java 客户端与服务器建立 TCP 连接
// 服务器端
import java.net.ServerSocket;
import java.net.Socket;
public class HandshakeServer {
public static void main(String[] args) throws Exception {
// 1. 启动服务器,监听端口 8080(状态:LISTEN)
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("服务器启动,等待连接...");
// 2. 阻塞等待客户端连接(三次握手在此过程中完成)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接:" + clientSocket.getInetAddress());
// 3. 连接建立后(状态:ESTABLISHED),可进行数据传输
// ...
}
}
}
// 客户端
import java.net.Socket;
public class HandshakeClient {
public static void main(String[] args) throws Exception {
// 1. 发起连接(触发三次握手)
System.out.println("发起连接请求...");
Socket socket = new Socket("localhost", 8080); // 三次握手完成后返回
// 2. 连接建立(状态:ESTABLISHED)
System.out.println("连接已建立,服务器地址:" + socket.getInetAddress());
// ...
}
}
执行现象:
- 服务器启动后调用
accept()
,进入阻塞状态(等待客户端连接,对应LISTEN
状态)。 - 客户端创建
Socket
时,底层自动发送第一次握手的 SYN 报文。 - 服务器收到 SYN 后,发送第二次握手的 SYN + ACK 报文。
- 客户端收到后,发送第三次握手的 ACK 报文。
- 三次握手完成,
accept()
方法返回,双方进入ESTABLISHED
状态,可开始通信。
六、总结
TCP 三次握手是建立可靠连接的必要过程,通过三次报文交互:
- 确认了客户端和服务器的发送/接收能力均正常。
- 协商了初始序列号,为后续数据的有序传输、确认和重传奠定基础。
- 避免了无效连接请求导致的服务器资源浪费。
在 Java 开发中,三次握手由 Socket
和 ServerSocket
底层自动实现,开发者只需关注连接建立后的业务逻辑,但理解其原理有助于排查连接超时、连接失败等网络问题(如半连接队列满、SYN 报文被防火墙拦截等)。