Java 提供 java.net 包原生支援 TCP/UDP 通信。本文系統講解:InetAddress 地址封裝、DatagramSocket/UDP 無連接通信、ServerSocket/Socket TCP 面向連接通信、多線程/線程池服務端模型、NIO 非阻塞演進方向,附帶可直接運行的完整代碼範例。
基礎知識速查
- 回環地址:
127.0.0.1 / ::1 (IPv6),本機內部通信,無需網卡
- 端口範圍:0-65535,0-1023 為知名端口 (需特權),應用建議用 1024+
- 協議選擇:TCP 可靠有序 (Web、DB、RPC);UDP 低延遲無連接 (DNS、流媒體、心跳、廣播)
UDP 通信:DatagramSocket + DatagramPacket
核心類速覽
| 類 | 關鍵構造/方法 | 用途 |
DatagramSocket | new DatagramSocket() / new DatagramSocket(port) / send(p) / receive(p) | 發送/接收數據報,綁定本地端口 |
DatagramPacket | new DatagramPacket(buf, len) (接收) / new DatagramPacket(buf, len, addr, port) (發送) | 數據報容器,攜帶數據、地址、端口 |
InetAddress | getByName(host) / getLocalHost() / getHostAddress() | IP 地址封裝 |
UDP 發送端
public class UdpSender {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket()) {
String msg = "Hello UDP @ " + Instant.now();
byte[] data = msg.getBytes(StandardCharsets.UTF_8);
InetAddress target = InetAddress.getByName("192.168.1.100");
int port = 9000;
DatagramPacket packet = new DatagramPacket(data, data.length, target, port);
socket.send(packet);
System.out.println("已發送: " + msg);
}
}
}
UDP 接收端
public class UdpReceiver {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket(9000)) {
byte[] buffer = new byte[4096];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP 接收端就緒,端口 9000...");
while (true) {
socket.receive(packet); // 阻塞等待
String msg = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
System.out.printf("收自 %s:%d → %s%n",
packet.getAddress().getHostAddress(), packet.getPort(), msg);
packet.setLength(buffer.length); // 重置長度,供下次接收
}
}
}
}
TCP 通信:ServerSocket + Socket
核心類速覽
| 類 | 關鍵構造/方法 | 用途 |
ServerSocket | new ServerSocket(port) / accept() → 返回新 Socket | 服務端監聽,accept() 阻塞直到客戶端連接 |
Socket | new Socket(host, port) / getInputStream() / getOutputStream() | 客戶端發起連接,雙向流通信 |
TCP 服務端:單線程版 (教學用)
public class TcpServerSimple {
public static void main(String[] args) throws IOException {
try (ServerSocket server = new ServerSocket(8080)) {
System.out.println("TCP 服務端啟動,端口 8080");
while (true) {
try (Socket client = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) {
System.out.println("客戶端連接: " + client.getInetAddress());
String line;
while ((line = in.readLine()) != null) {
System.out.println("收到: " + line);
out.println("Echo: " + line); // 自動 flush
if ("quit".equalsIgnoreCase(line)) break;
}
} catch (IOException e) {
System.err.println("客戶端通信異常: " + e.getMessage());
}
}
}
}
}
TCP 服務端:多線程版 (生產雛形)
public class TcpServerThreadPool {
private static final ExecutorService POOL = Executors.newFixedThreadPool(50);
public static void main(String[] args) throws IOException {
try (ServerSocket server = new ServerSocket(8080)) {
System.out.println("TCP 服務端啟動 (線程池), 端口 8080");
while (true) {
Socket client = server.accept();
POOL.submit(() -> handleClient(client));
}
}
}
private static void handleClient(Socket client) {
String clientInfo = client.getInetAddress() + ":" + client.getPort();
try (BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) {
System.out.println("處理客戶端: " + clientInfo);
String line;
while ((line = in.readLine()) != null) {
String response = processRequest(line); // 業務邏輯
out.println(response);
if ("quit".equalsIgnoreCase(line)) break;
}
} catch (IOException e) {
System.err.println("客戶端 " + clientInfo + " 異常: " + e.getMessage());
} finally {
try { client.close(); } catch (IOException ignored) {}
}
}
private static String processRequest(String req) {
// 實際業務:解析協議、調用服務、返回結果
return "Server processed: " + req;
}
}
TCP 客戶端
public class TcpClient {
public static void main(String[] args) throws IOException {
String host = args.length > 0 ? args[0] : "127.0.0.1";
int port = args.length > 1 ? Integer.parseInt(args[1]) : 8080;
try (Socket socket = new Socket(host, port);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
System.out.println("已連接至 " + host + ":" + port);
String line;
while ((line = keyboard.readLine()) != null) {
out.println(line);
String response = in.readLine();
if (response == null) break;
System.out.println("服務端: " + response);
if ("quit".equalsIgnoreCase(line)) break;
}
}
}
}
進階主題:從 BIO 到 NIO
| 模型 | 特點 | 適用場景 | Java API |
| BIO (阻塞) | 每連接一線程,簡單但擴展性差 | 連接數 < 1000,低併發 | ServerSocket/Socket |
| NIO (非阻塞) | 單線程管理多連接,Selector 輪詢就緒事件 | 高併發、長連接 (IM、推送) | ServerSocketChannel, Selector |
| AIO (異步) | 內核完成 I/O 後回調,真正異步 | 超高併發、大文件傳輸 | AsynchronousServerSocketChannel |
| Netty | NIO 封裝框架,事件驅動、管道、零拷貝、內存池 | 生產首選:高性能、穩定、生態豐富 | netty-all |
關鍵最佳實踐
- 顯式指定編碼:
StandardCharsets.UTF_8,杜絕亂碼
- try-with-resources:保證 Socket/流關閉,防洩漏
- 線程池隔離:業務處理提交線程池,accept 線程專職接受連接
- 協議分幀:TCP 字節流需應用層定義邊界 (長度前綴/分隔符/序列化)
- 心跳/超時:長連接必須有空閒檢測機制
- 優雅關閉:關閉 ServerSocket 觸發 accept 異常 → 關閉線程池 → 等待任務完成
總結
Java 原生 TCP/UDP API 簡潔強大,掌握 ServerSocket/Socket 與 DatagramSocket/DatagramPacket 核心類、多線程服務端模型、編碼與資源管理最佳實踐,即可構建健壯網絡應用。高併發場景建議直接使用 Netty,站在巨人肩膀上專注業務邏輯。