计算机网络:TCP 的三次握手与四次挥手(基于 Java)在计算机网络的世界里,TCP(传输控制协议)犹如一位严谨的通信管家,确保数据在网络中可靠传输。而三次握手和四次挥手作为 TCP 建立和终止连接的关键环节,是实现可靠通信的核心所在。
三次握手是 TCP 协议中建立连接的过程。简单来说,就是客户端和服务器通过三次数据包的交互,来确认双方的发送和接收能力都处于正常状态,最终建立起一条可靠的通信链路。它就像两个人打电话,先问 “你能听到我说话吗”,对方回应 “能听到,你能听到我吗”,再回应 “能听到”,这样通话连接就建立好了。
四次挥手则是 TCP 协议中终止连接的过程。由于 TCP 连接是全双工的,双方可以同时发送数据,所以关闭连接时需要双方分别确认,通过四次数据包交互,逐步关闭各自的发送通道,最终释放连接所占用的资源。这好比通话结束时,一方说 “我说完了”,对方回应 “知道了”,然后对方说 “我也说完了”,最后一方回应 “好的,再见”,通话连接就此终止。
三次握手的作用有:
(1)确认通信能力:通过三次交互,双方都能确认对方既能发送数据,也能接收数据,避免了因一方通信能力故障而导致的数据传输失败。
(2)同步序列号:TCP 通过序列号来保证数据的有序性和完整性,三次握手过程中,双方会交换初始序列号,为后续数据传输时的排序和去重打下基础。
(3)防止无效连接:可以避免网络中滞留的过期连接请求报文被服务器误接收,从而防止服务器因建立无效连接而浪费资源。
而四次挥手的主要作用有:
(1)优雅释放资源:确保双方已传输的数据都被正确接收,不会在连接关闭时出现数据丢失的情况。
(2)处理半关闭状态:支持一方先关闭发送通道,但仍能继续接收数据的 “半关闭” 场景,以适应实际通信中可能出现的不对称关闭需求。
(3)避免连接残留:通过双方的确认,彻底释放连接所占用的端口、缓存等资源,防止资源泄漏。
三次握手的主要原理为:
(1)第一次握手:客户端向服务器发送一个 SYN 报文段,其中包含客户端的初始序列号,并将 SYN 标志位设为 1。此时客户端进入 SYN_SENT 状态,等待服务器的响应。
(2)第二次握手:服务器收到 SYN 报文后,会返回一个 SYN+ACK 报文段。其中 SYN 标志位设为 1,包含服务器的初始序列号;ACK 标志位设为 1,确认号为客户端初始序列号 + 1,表示已收到客户端的 SYN 报文。此时服务器进入 SYN_RCVD 状态。
(3)第三次握手:客户端收到 SYN+ACK 报文后,发送一个 ACK 报文段,ACK 标志位设为 1,确认号为服务器初始序列号 + 1,表示已收到服务器的 SYN 报文。客户端发送完毕后进入 ESTABLISHED 状态,服务器收到 ACK 后也进入 ESTABLISHED 状态,双方开始传输数据。
而四次挥手的原理为:
(1)第一次挥手:假设客户端主动关闭连接,发送一个 FIN 报文段,FIN 标志位设为 1,序列号为客户端已发送数据的最后一个字节序号 + 1。客户端进入 FIN_WAIT_1 状态,表明不再发送数据,但仍可接收数据。
(2)第二次挥手:服务器收到 FIN 报文后,返回一个 ACK 报文段,确认号为客户端 FIN 的序列号 + 1,表明已收到关闭请求。服务器进入 CLOSE_WAIT 状态,客户端收到 ACK 后进入 FIN_WAIT_2 状态,等待服务器的关闭通知。
(3)第三次挥手:服务器完成剩余数据传输后,向客户端发送 FIN 报文段,FIN 标志位设为 1,序列号为服务器已发送数据的最后一个字节序号 + 1。服务器进入 LAST_ACK 状态,等待客户端确认。
(4)第四次挥手:客户端收到 FIN 报文后,返回 ACK 报文段,确认号为服务器 FIN 的序列号 + 1,并进入 TIME_WAIT 状态。服务器收到 ACK 后进入 CLOSED 状态;客户端等待一段时间(通常为 2MSL)后,也进入 CLOSED 状态,连接彻底释放。
三次握手的应用场景主要为:
(1)HTTP/HTTPS 通信:当我们在浏览器中输入网址访问网页时,浏览器与 Web 服务器之间首先要通过三次握手建立 TCP 连接,然后才能传输网页数据。
(2)文件传输:像 FTP 协议进行文件传输时,依赖 TCP 连接,三次握手保证了大文件在传输过程中不会因连接问题而丢失数据。
(3)邮件发送:邮件客户端与邮件服务器之间发送邮件时,也需要通过三次握手建立 TCP 连接,确保邮件内容能完整送达。
而四次挥手的应用场景主要为;
(1)网页浏览结束:当我们关闭浏览器或者从一个网页跳转到另一个网页时,客户端与原 Web 服务器会通过四次挥手释放 TCP 连接。
(2)文件传输完成:FTP 文件传输完成后,双方会进行四次挥手关闭连接,释放所占用的资源。
(3)网络异常中断处理:当网络出现故障或者连接超时等异常情况时,通信双方会通过四次挥手来优雅地关闭连接。
三次握手的优缺点分别是:
优点:仅通过三次交互就可以确认双方的通信能力,在可靠性和效率之间取得了较好的平衡;同步序列号机制为数据的有序传输提供了保障;能有效防止过期连接请求导致的资源浪费。
缺点:相比 UDP 等无连接协议,建立连接的耗时更长,不适合对实时性要求极高的场景,如实时视频通话;可能会遭受 SYN 洪水攻击,攻击者发送大量伪造的 SYN 报文,导致服务器资源耗尽。
而四次挥手的优缺点主要有;
优点:支持半关闭状态,能适应复杂的通信场景;通过 TIME_WAIT 状态确保最后一个 ACK 被收到,避免连接残留;能彻底释放资源,防止端口被长期占用。
缺点:四次交互增加了连接关闭的耗时,在高并发场景下可能会影响系统的吞吐量;TIME_WAIT 状态默认等待时间较长,若短时间内大量连接关闭,可能会导致端口耗尽。
在 Java 中,我们可以使用 Socket 和 ServerSocket 来模拟 TCP 的连接建立和关闭过程,其中就隐含了三次握手和四次挥手的操作(由操作系统底层实现)。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
// 服务端
public class TCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket clientSocket = null;
InputStream in = null;
OutputStream out = null;
try {
// 创建ServerSocket,监听指定端口
serverSocket = new ServerSocket(8888);
System.out.println("服务器启动,等待客户端连接...");
// 等待客户端连接,accept()方法会阻塞,直到有客户端连接
// 这里隐含了三次握手的过程
clientSocket = serverSocket.accept();
System.out.println("客户端连接成功,客户端地址:" + clientSocket.getInetAddress());
// 获取输入流和输出流
in = clientSocket.getInputStream();
out = clientSocket.getOutputStream();
// 读取客户端发送的数据
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println("收到客户端数据:" + new String(buffer, 0, len));
// 向客户端发送数据
String response = "Hello, Client!";
out.write(response.getBytes());
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭资源,隐含了四次挥手的过程
if (in != null) in.close();
if (out != null) out.close();
if (clientSocket != null) clientSocket.close();
if (serverSocket != null) serverSocket.close();
System.out.println("服务器连接关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
// 客户端
public class TCPClient {
public static void main(String[] args) {
Socket socket = null;
OutputStream out = null;
InputStream in = null;
try {
// 创建Socket,连接服务器,这里隐含了三次握手的过程
socket = new Socket("127.0.0.1", 8888);
System.out.println("连接服务器成功");
// 获取输入流和输出流
out = socket.getOutputStream();
in = socket.getInputStream();
// 向服务器发送数据
String data = "Hello, Server!";
out.write(data.getBytes());
out.flush();
// 读取服务器返回的数据
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println("收到服务器数据:" + new String(buffer, 0, len));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭资源,隐含了四次挥手的过程
if (out != null) out.close();
if (in != null) in.close();
if (socket != null) socket.close();
System.out.println("客户端连接关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在这两段代码中,当客户端执行new Socket("127.0.0.1", 8888)和服务器执行serverSocket.accept()时,底层会完成三次握手过程以建立连接。而当我们关闭 Socket 等资源时,底层则会进行四次挥手来终止连接。需要注意的是,三次握手和四次挥手的具体实现是在操作系统的 TCP/IP 协议栈中,Java 代码只是通过 API 触发了这些过程。