Java 中的 TCP 协议 主要通过 java.net 包中的 Socket 和 ServerSocket 两个核心类来实现。
Java 并没有直接“实现 TCP 协议”(TCP 协议栈由操作系统内核完成),而是提供了高级抽象,让开发者通过面向流的 socket 接口使用 TCP 的可靠、面向连接、字节流传输特性。
1. 核心类对比(2026 年视角仍未变化)
| 类名 | 角色 | 主要用途 | 是否阻塞式(默认) | 典型构造方法示例 |
|---|---|---|---|---|
java.net.Socket | 客户端 / 已连接的端点 | 主动发起连接、读写数据 | 是 | new Socket("127.0.0.1", 8080) |
java.net.ServerSocket | 服务端监听 | 绑定端口、监听连接、产生新 Socket | 是 | new ServerSocket(8080) |
Socket 的流 | — | getInputStream() / getOutputStream() | — | 用于实际的字节读写 |
2. TCP 在 Java 中的关键特性(开发者视角)
- 三次握手:隐藏在
Socket构造器 /ServerSocket.accept()内部完成 - 可靠传输:重传、确认、流量控制、拥塞控制 → 全部由 OS 内核处理,Java 不感知
- 字节流:无消息边界(不像 UDP 有数据报边界)
- 有序性:保证发送顺序 = 接收顺序
- 全双工:读写可同时进行(但应用层需自行处理并发)
- 优雅关闭:
shutdownInput()/shutdownOutput()/close()支持半关闭
3. 最经典的单线程阻塞式 Echo Server & Client 示例(2026 年仍是最基础面试写法)
Server 端(EchoServer.java)
import java.io.*;
import java.net.*;
public class EchoServer {
public static void main(String[] args) {
int port = 8888;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("TCP Echo Server started on port " + port);
while (true) { // 持续接受新连接
// 阻塞等待客户端连接(三次握手在此完成)
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress());
// 为每个客户端分配一个线程(生产中强烈推荐线程池)
new Thread(() -> handleClient(clientSocket)).start();
}
} catch (IOException e) {
System.err.println("Server error: " + e.getMessage());
}
}
private static void handleClient(Socket socket) {
try (
socket; // Java 9+ 自动关闭
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
socket.getOutputStream(), true)
) {
String line;
while ((line = in.readLine()) != null) {
System.out.println("Received: " + line);
out.println("Echo: " + line); // 回显
out.flush();
}
} catch (IOException e) {
System.err.println("Client error: " + e.getMessage());
} finally {
System.out.println("Client disconnected");
}
}
}
Client 端(EchoClient.java)
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class EchoClient {
public static void main(String[] args) {
String host = "localhost";
int port = 8888;
try (
Socket socket = new Socket(host, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
Scanner scanner = new Scanner(System.in)
) {
System.out.println("Connected to " + host + ":" + port);
System.out.println("Type messages (type 'bye' to exit):");
String userInput;
while (!(userInput = scanner.nextLine()).equalsIgnoreCase("bye")) {
out.println(userInput); // 发送
String response = in.readLine(); // 接收
System.out.println("Server reply: " + response);
}
out.println("bye");
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + host);
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
}
}
}
4. 生产环境中必须知道的进阶点(面试常问)
| 问题/场景 | 推荐做法(2026 年主流) | 关键类/方法 |
|---|---|---|
| 处理多个客户端 | 使用线程池(Executors.newFixedThreadPool / newCachedThreadPool)或 NIO / Netty | ExecutorService |
| 非阻塞 / 高并发 | 切换到 NIO(java.nio.channels)或直接使用 Netty / Vert.x | Selector, SocketChannel, ServerSocketChannel |
| 心跳检测 | 定时发送 PING/PONG,或使用 SO_KEEPALIVE + 自定义应用层心跳 | setKeepAlive(true), setSoTimeout() |
| 半包/粘包 | TCP 是流式 → 必须自己定义消息边界(长度前缀、定长、分隔符、协议如 Protobuf/JSON Length) | — |
| 优雅关闭连接 | 先 shutdownOutput() → 读完剩余数据 → 再 close() | shutdownInput(), shutdownOutput() |
| 超时控制 | connect timeout / read timeout / write timeout | setSoTimeout(), Socket connect() 重载 |
| TLS / SSL | 使用 SSLSocket / SSLServerSocket(或直接用 Netty 的 SslHandler) | SSLSocketFactory |
| 零拷贝优化 | Java NIO 的 transferTo() / transferFrom() 或 Netty 的零拷贝支持 | FileChannel.transferTo() |
5. 常见面试追问一览
- Socket 和 ServerSocket 区别?
- accept() 做了什么?(完成三次握手,返回新的 Socket)
- close() 和 shutdownOutput() 区别?
- 如何判断客户端异常断开?(read 返回 -1 或抛异常)
- 为什么生产不用单线程 accept + 处理?(阻塞导致无法接受新连接)
- BIO / NIO / AIO / Netty 对比?
- TCP 粘包怎么解决?(最少说出 4 种方案)
需要哪一部分更详细的代码示例(线程池版、多路复用 NIO 版、Netty 版、心跳版、Protobuf 定长协议版等),或者想看某个具体场景的完整实现?可以直接告诉我。