Java java.net.Socket 與 ServerSocket 提供了最基礎的阻塞式 TCP 通訊能力。雖然 NIO (Non-blocking I/O) 更適合高併發,但阻塞模型因程式碼直觀、除錯容易,仍是中小型應用、內部工具、教學示範的首選。
public class EchoServer {
private final int port = 8000;
private ServerSocket serverSocket;
public EchoServer() throws IOException {
serverSocket = new ServerSocket(port);
System.out.println("Server Started on port " + port);
}
public String echo(String msg) {
return "echo: " + msg;
}
private PrintWriter getWriter(Socket socket) throws IOException {
return new PrintWriter(socket.getOutputStream(), true); // auto-flush
}
private BufferedReader getReader(Socket socket) throws IOException {
return new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
public void service() {
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept(); // 阻塞等待客戶端連線
System.out.println("New connection: " + socket.getInetAddress() + ":" + socket.getPort());
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
String msg;
while ((msg = br.readLine()) != null) {
System.out.println("Received: " + msg);
pw.println(echo(msg));
if ("bye".equalsIgnoreCase(msg)) break; // 約定結束指令
}
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(socket);
}
}
}
private void closeQuietly(Socket socket) {
if (socket != null) try { socket.close(); } catch (IOException ignored) {}
}
public static void main(String[] args) throws IOException {
new EchoServer().service();
}
}
serverSocket.accept() 阻塞直到有客戶端連入,回傳對應的 Socket。getInputStream()/getOutputStream() 取得位元流,包裝為 BufferedReader/PrintWriter 處理文字行。public class EchoClient {
private final String host = "localhost";
private final int port = 8000;
private Socket socket;
public EchoClient() throws IOException {
socket = new Socket(host, port); // 連線至伺服器
}
private PrintWriter getWriter(Socket socket) throws IOException {
return new PrintWriter(socket.getOutputStream(), true);
}
private BufferedReader getReader(Socket socket) throws IOException {
return new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
public void talk() throws IOException {
try (BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
BufferedReader console = new BufferedReader(new InputStreamReader(System.in))) {
String msg;
while ((msg = console.readLine()) != null) {
pw.println(msg); // 發送給伺服器
System.out.println(br.readLine()); // 接收回應
if ("bye".equalsIgnoreCase(msg)) break;
}
} finally {
closeQuietly(socket);
}
}
private void closeQuietly(Socket socket) {
if (socket != null) try { socket.close(); } catch (IOException ignored) {}
}
public static void main(String[] args) throws IOException {
new EchoClient().talk();
}
}
boolean isConnected = socket.isConnected() && !socket.isClosed();
isConnected():曾經成功連線過即為 true(即使已斷開)。
isClosed():close() 被呼叫後為 true。
| 策略 | 適用場景 | 範例 |
|---|---|---|
| 特殊結束標記 | 文字行協議 | 雙方約定 "bye" 結束 |
| 長度前綴 | 二進位資料流 | 先發 4 bytes 長度,再發內容 |
| 流尾關閉 | 單向傳輸 | 發送端 shutdownOutput(),接收端讀到 -1 |
socket.shutdownOutput(); // 僅關閉輸出流,仍可讀取對方回應
socket.shutdownInput(); // 僅關閉輸入流
// 最後仍需呼叫 socket.close() 釋放底層資源
使用 ExecutorService 管理固定大小線程池,主執行緒只負責 accept(),實際通訊交給 Worker 執行緒:
public class MultiEchoServer {
private final int port = 8000;
private ServerSocket serverSocket;
private final ExecutorService executorService;
public MultiEchoServer() throws IOException {
serverSocket = new ServerSocket(port);
int poolSize = Runtime.getRuntime().availableProcessors() * 4;
executorService = Executors.newFixedThreadPool(poolSize);
System.out.println("Multi-thread Server Started, pool size: " + poolSize);
}
public void service() {
while (true) {
try {
Socket socket = serverSocket.accept();
executorService.execute(new Handler(socket)); // 分派任務
} catch (IOException e) {
e.printStackTrace();
}
}
}
static class Handler implements Runnable {
private final Socket socket;
Handler(Socket socket) { this.socket = socket; }
@Override public void run() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) {
System.out.println("Handler: " + socket.getInetAddress());
String msg;
while ((msg = br.readLine()) != null) {
pw.println("echo: " + msg);
if ("bye".equalsIgnoreCase(msg)) break;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(socket);
}
}
private void closeQuietly(Socket s) { if (s != null) try { s.close(); } catch (IOException ignored) {} }
}
public static void main(String[] args) throws IOException {
new MultiEchoServer().service();
}
}
Socket.setReceiveBufferSize()/setSendBufferSize() 根據網路 MTU 調整(預設 64KB)。阻塞式 Socket 雖非高併發首選,但其「所見即所得」的程式模型極利於快速原型開發與教學。掌握 accept-read/write-close 生命週期、訊息邊界處理、線程池分派,即可構建穩健的 TCP 服務。若需支撐 C10K 以上併發,請轉向 NIO/Netty 架構。