Echo 服務是網路程式設計的「Hello World」。伺服端接收客戶端傳送的文字,原樣回傳 (加前綴 "Echo: "),直到雙方約定結束 (輸入 "BYE")。本文提供完整、可直接編譯運行的 EchoServer1、EchoClient1 原始碼,並詳解關鍵 API、執行步驟與常見錯誤。
import java.io.*;
import java.net.*;
public class EchoServer1 {
public static void main(String[] args) {
try (ServerSocket server = new ServerSocket(8500)) {
System.out.println("Echo Server started on port 8500. Waiting for client...");
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)) {
out.println("Hello! Enter BYE to exit.");
String line;
while ((line = in.readLine()) != null) {
if (line.trim().equalsIgnoreCase("BYE")) {
out.println("Echo: BYE - Goodbye!");
break;
}
out.println("Echo: " + line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.*;
public class EchoClient1 {
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java EchoClient1 ");
return;
}
String host = args[0];
int port = 8500;
try (Socket socket = "localhost".equals(host) ?
new Socket(InetAddress.getLocalHost(), port) :
new Socket(InetAddress.getByName(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 console = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("Connected to " + host + ":" + port);
String serverMsg = in.readLine(); // 讀取歡迎訊息
System.out.println("Server: " + serverMsg);
String input;
while ((input = console.readLine()) != null) {
out.println(input); // 發送給伺服器
if ("bye".equalsIgnoreCase(input)) break;
String echo = in.readLine(); // 讀取回應
System.out.println("Server: " + echo);
}
} catch (UnknownHostException e) {
System.err.println("Unknown host: " + host);
} catch (IOException e) {
System.err.println("I/O Error: " + e.getMessage());
}
}
}
| API | 用途 | 關鍵參數 |
|---|---|---|
ServerSocket(int port) | 建立伺服器監聽埠口 | port=8500,0 表系統自動分配 |
server.accept() | 阻塞等待客戶端連線,返回新 Socket | 每個客戶端對應一個 Socket |
Socket(InetAddress, int) | 客戶端主動連線 | 支援 hostname 或 IP |
BufferedReader | 行導向讀取 (以 \n/\r\n 分隔) | 包裝 InputStreamReader 指定 UTF-8 |
PrintWriter(..., true) | 行導向寫入,autoFlush=true | 呼叫 println() 自動 flush() |
try-with-resources | JDK 7+ 自動關閉資源 | 確保 Socket/Stream 正確釋放 |
EchoServer1.java、EchoClient1.javajavac EchoServer1.java
javac EchoClient1.java
java EchoServer1
輸出:Echo Server started on port 8500. Waiting for client...
java EchoClient1 localhost
或跨機器:java EchoClient1 192.168.1.100
BYE 結束。| 錯誤現象 | 原因 | 解決 |
|---|---|---|
Connection refused | 伺服端未啟動、埠口錯誤、防火牆攔截 | 確認伺服端運行、埠口一致、防火牆放行 8500 |
BindException: Address already in use | 埠口被佔用 (前次進程未完全釋放) | lsof -i :8500 找到 PID kill,或換埠口 |
| 中文亂碼 | 編碼不一致 (伺服端/客戶端/終端機) | 統一指定 StandardCharsets.UTF_8,終端機設定 UTF-8 |
| 客戶端輸入無回應 | 伺服端未 flush() 或客戶端 readLine() 等待換行符 | PrintWriter(..., true) 啟用 autoFlush,確保發送 \n |
UnknownHostException | 主機名無法解析 | 用 IP 替代、檢查 /etc/hosts 或 DNS |
accept() 後的處理移入 new Thread(() -> handle(client)).start(),支援多客戶端同時連線。"Echo [" + Instant.now() + "]: " + line。/time 回傳伺服器時間、/ip 回傳客戶端 IP。SSLServerSocketFactory/SSLSocketFactory,需產生 keystore。ServerSocketChannel、Selector 實現單執行緒高併發。Echo 範例雖簡單,却涵蓋了 Java Socket 程式設計的完整生命週期:ServerSocket → accept() → Stream → readLine()/println() → close()。掌握此範例,即掌握了所有 TCP 文字協定 (HTTP、SMTP、POP3、IRC、自訂協定) 的開發基礎。建議動手運行、修改、擴充,建立肌肉記憶。