經典阻塞 IO (BIO) 模式,主執行緒 ServerSocket.accept() 循環等待連線,每新連線建立一個獨立執行緒處理讀寫。適合連線數較少(<500)、業務邏輯含阻塞操作(資料庫、外部 API)的場景。現代高併發(萬級連線)建議改用 NIO (Netty) 或虛擬執行緒 (Java 21+)。
import java.io.*;
import java.net.*;
public class ThreadedEchoServer {
private static final int PORT = 8000;
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
serverSocket.setReuseAddress(true); // 避免 TIME_WAIT 導致重啟失敗
System.out.println("伺服器啟動,監聽埠口: " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept(); // 阻塞等待
// 每個連線派發新執行緒
new ClientHandler(clientSocket).start();
}
} catch (IOException e) {
System.err.println("伺服器異常: " + e.getMessage());
e.printStackTrace();
}
}
private static class ClientHandler extends Thread {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
String clientInfo = clientSocket.getInetAddress() + ":" + clientSocket.getPort();
System.out.println("新連線: " + clientInfo);
try (BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println("收到 [" + clientInfo + "]: " + line);
// 回應邏輯
if ("END".equalsIgnoreCase(line.trim())) {
out.println("Goodbye!");
break;
}
out.println("Echo: " + line);
}
} catch (IOException e) {
System.err.println("連線異常 [" + clientInfo + "]: " + e.getMessage());
} finally {
closeQuietly(clientSocket);
System.out.println("連線關閉: " + clientInfo);
}
}
private void closeQuietly(Socket socket) {
if (socket != null) {
try { socket.close(); } catch (IOException ignored) {}
}
}
}
}
| 環節 | 做法 | 理由 |
|---|---|---|
| 字元編碼 | 明確指定 UTF-8 | 避免平台預設編碼不一致導致亂碼 |
| 緩衝讀取 | BufferedReader.readLine() | 按行讀取,適配文本協定;二進制請用 InputStream.read() |
| 自動刷新 | PrintWriter(..., true) | println 後自動 flush(),確保即時送達 |
| 資源釋放 | try-with-resources + finally | 保證 Socket/Stream 關閉,防止檔案描述符洩漏 |
| 優雅關閉 | 檢測 "END" 指令主動斷開 | 示範應用層心跳/指令控制 |
| SO_REUSEADDR | serverSocket.setReuseAddress(true) | 重啟時立即復用埠口,開發測試必備 |
ExecutorService pool = Executors.newFixedThreadPool(200),pool.execute(new ClientHandler(socket)),限制最大並發。Selector 多路複用,支援萬級連線(Netty、Mina 框架封裝)。Executors.newVirtualThreadPerTaskExecutor(),輕量級執行緒,程式碼幾乎不改即可支援百萬連線。import java.util.concurrent.*;
public class PooledThreadServer {
private static final int PORT = 8000;
private static final ExecutorService POOL = Executors.newFixedThreadPool(200);
public static void main(String[] args) {
try (ServerSocket server = new ServerSocket(PORT)) {
server.setReuseAddress(true);
System.out.println("Pooled Server started on " + PORT);
while (true) {
Socket client = server.accept();
POOL.execute(new ClientHandler(client)); // 提交任務而非 new Thread
}
} catch (IOException e) {
e.printStackTrace();
} finally {
POOL.shutdown();
}
}
// ClientHandler 改實作 Runnable,邏輯同前
}
import java.io.*;
import java.net.*;
public class EchoClient {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket("localhost", 8000);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader console = new BufferedReader(new InputStreamReader(System.in))) {
String input;
while ((input = console.readLine()) != null) {
out.println(input);
String response = in.readLine();
System.out.println("Server: " + response);
if ("END".equalsIgnoreCase(input)) break;
}
}
}
}
總結:Thread-per-Connection 是理解 Socket 多併發的基石,教學與中小規模專案足夠。生產環境高併發請直接上 Netty (NIO) 或 Java 21 虛擬執行緒,核心業務邏輯可無縫遷移。