Socket 是網路通訊的基礎,Java 提供 java.net.Socket (客戶端) 與 java.net.ServerSocket (伺服端) 實作 TCP 可靠串流傳輸。本文提供一個完整的回聲伺服器範例,展示阻塞式 I/O、多執行緒處理併發連線、優雅關閉與錯誤處理的最佳實務。
package com.example.socket;
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class EchoServer {
private static final int PORT = 8888;
private static final int MAX_THREADS = 50;
private final ExecutorService threadPool = Executors.newFixedThreadPool(MAX_THREADS);
private volatile boolean running = true;
public void start() {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
serverSocket.setSoTimeout(1000); // 定期檢查 running 旗標
System.out.println("伺服器啟動,監聽埠口: " + PORT);
while (running) {
try {
Socket clientSocket = serverSocket.accept();
System.out.println("新連線: " + clientSocket.getInetAddress());
threadPool.execute(new ClientHandler(clientSocket));
} catch (SocketTimeoutException e) {
// 超時繼續迴圈,檢查 running
} catch (IOException e) {
if (running) System.err.println("接受連線失敗: " + e.getMessage());
}
}
} catch (IOException e) {
System.err.println("伺服器啟動失敗: " + e.getMessage());
} finally {
shutdown();
}
}
public void stop() {
running = false;
}
private void shutdown() {
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(10, TimeUnit.SECONDS)) {
threadPool.shutdownNow();
}
} catch (InterruptedException e) {
threadPool.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("伺服器已關閉");
}
private static class ClientHandler implements Runnable {
private final Socket socket;
ClientHandler(Socket socket) { this.socket = socket; }
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println("收到: " + line);
if ("bye".equalsIgnoreCase(line.trim())) {
out.println("Goodbye!");
break;
}
out.println("Echo: " + line); // 回傳
}
} catch (IOException e) {
System.err.println("處理連線錯誤: " + e.getMessage());
} finally {
try { socket.close(); } catch (IOException ignored) {}
System.out.println("連線關閉: " + socket.getInetAddress());
}
}
}
public static void main(String[] args) {
EchoServer server = new EchoServer();
// 註冊關閉鉤子,支援 Ctrl+C 優雅關閉
Runtime.getRuntime().addShutdownHook(new Thread(server::stop));
server.start();
}
}
package com.example.socket;
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class EchoClient {
private static final String HOST = "localhost";
private static final int PORT = 8888;
public void start() {
try (Socket socket = new Socket(HOST, PORT);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
Scanner scanner = new Scanner(System.in)) {
System.out.println("已連線至伺服器。輸入訊息 (輸入 bye 結束):");
String input;
while (scanner.hasNextLine() && (input = scanner.nextLine()) != null) {
out.println(input); // 發送
String response = in.readLine(); // 接收回應
if (response == null) {
System.out.println("伺服器已關閉連線");
break;
}
System.out.println("伺服器回應: " + response);
if ("bye".equalsIgnoreCase(input.trim())) break;
}
} catch (UnknownHostException e) {
System.err.println("找不到主機: " + HOST);
} catch (IOException e) {
System.err.println("通訊錯誤: " + e.getMessage());
}
}
public static void main(String[] args) {
new EchoClient().start();
}
}
java com.example.socket.EchoServerjava com.example.socket.EchoClientEcho: ...bye 結束該客戶端連線。| 技術點 | 說明 |
|---|---|
| 執行緒池 | 使用 Executors.newFixedThreadPool 限制最大併發執行緒,避免連線暴增耗盡資源。 |
| 字元編碼 | 明確指定 UTF-8,避免跨平台亂碼。 |
| 緩衝讀寫 | BufferedReader/PrintWriter 提供行導向讀寫,簡化協定處理。 |
| 自動刷新 | PrintWriter(..., true) 啟用 autoFlush,println 後立即發送。 |
| 優雅關閉 | 關閉鉤子 + ExecutorService.shutdown() 確保資源釋放。 |
| Socket 選項 | setSoTimeout 讓 accept() 定期超時,能檢查關閉旗標。 |
Selector、ServerSocketChannel、SocketChannel 處理上萬併發連線 (C10K 問題)。SSLServerSocketFactory、SSLSocketFactory 建立安全通道。serverSocket.setReuseAddress(true)。InputStreamReader/OutputStreamWriter 明確指定。-Xss (如 256k)、改用 NIO、或限制 maxTotal 連線數。掌握阻塞式 Socket 程式設計是理解網路通訊基礎的關鍵,也是進階學習 NIO、Netty 等高效能框架的基石。