accept() 阻塞等待客戶端連線。accept() 返回新的 Socket 代表該連線,綁定客戶端 IP/埠。accept() 下一個連線。import java.io.*;
import java.net.*;
import java.util.*;
public class SSServer {
public static void main(String[] args) throws IOException {
System.out.println("Server starting... (Port 10000)");
try (ServerSocket server = new ServerSocket(10000)) {
while (true) {
Socket client = server.accept(); // 阻塞直到連線
System.out.println("Accepting connection from " + client.getInetAddress());
// 為每個客戶端啟動獨立執行緒
new Thread(new ServerHandler(client)).start();
}
}
}
}
class ServerHandler implements Runnable {
private final Socket socket;
ServerHandler(Socket socket) { this.socket = socket; }
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
Calendar cal = Calendar.getInstance();
String cmd;
// 讀取命令迴圈
while ((cmd = in.readLine()) != null) {
cmd = cmd.toUpperCase().trim();
if (cmd.startsWith("BYE")) break;
switch (cmd) {
case "DATE":
case "TIME":
out.println(cal.getTime());
break;
case "DOM":
out.println(cal.get(Calendar.DAY_OF_MONTH));
break;
case "DOW":
String[] days = {"", "SUNDAY","MONDAY","TUESDAY","WEDNESDAY","THURSDAY","FRIDAY","SATURDAY"};
out.println(days[cal.get(Calendar.DAY_OF_WEEK)]);
break;
case "DOY":
out.println(cal.get(Calendar.DAY_OF_YEAR));
break;
case "PAUSE":
try { Thread.sleep(3000); } catch (InterruptedException ignored) {}
out.println("Paused 3 seconds.");
break;
default:
out.println("Unknown command: " + cmd);
}
}
} catch (IOException e) {
System.err.println("Connection error: " + e.getMessage());
} finally {
System.out.println("Closing connection from " + socket.getInetAddress());
try { socket.close(); } catch (IOException ignored) {}
}
}
}
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class SSClient {
public static void main(String[] args) throws IOException {
try (Socket socket = new Socket("localhost", 10000);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
Scanner scanner = new Scanner(System.in)) {
System.out.println("Connected. Commands: DATE, TIME, DOM, DOW, DOY, PAUSE, BYE");
String input;
while (true) {
System.out.print("> ");
input = scanner.nextLine();
out.println(input);
if (input.trim().equalsIgnoreCase("BYE")) break;
String response = in.readLine();
System.out.println("Server: " + response);
}
}
}
}
// 固定大小執行緒池,避免連線風暴耗盡記憶體
ExecutorService pool = Executors.newFixedThreadPool(50); // 或用 ThreadPoolExecutor 自訂
while (true) {
Socket client = server.accept();
pool.execute(new ServerHandler(client)); // 提交任務而非 new Thread()
}
傳統 BIO (Blocking I/O):一連線一執行緒,C10K 問題。
Java NIO (java.nio.channels):單執行緒透過 Selector 多路複用管理成千上萬連線。
現代框架 Netty 封裝 NIO 細節,提供事件驅動架構,是高效能伺服器標準選擇。
new InputStreamReader(is, StandardCharsets.UTF_8),避免平台預設編碼不一致導致亂碼。BufferedReader.readLine() 處理換行符 (\n, \r\n, \r) 跨平台相容。PrintWriter(String, true) 第二參數 autoFlush=true,println 會自動刷新,適合行導向協議。使用 try-with-resources (JDK 7+) 自動按相反順序關閉,即使發生異常也保證釋放:
try (Socket socket = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {
// 業務邏輯
} // 自動關閉 out, in, socket
| 方法 | 用途 |
|---|---|
setSoTimeout(ms) | accept() 超時,定期檢查關閉旗標優雅關閉 |
setReuseAddress(true) | 重啟伺服器時避免 "Address already in use" (TIME_WAIT 狀態) |
setReceiveBufferSize(bytes) | 調整接收緩衝區 (OS 層面),大流量可適當調大 |
getInetAddress() | 取得綁定的本地 IP (多網卡環境可指定綁定特定 IP) |
BIO (Thread-per-Connection)
↓ 連線數增長 → 線程上下文切換開銷大、記憶體爆炸
NIO (Selector + Channel + Buffer)
↓ API 複雜、需處理半包/粘包、Selector 空輪詢 Bug (JDK 1.6 修復)
Netty (EventLoop + Pipeline + Codec)
↓ 生產級框架:零拷貝、內存池、流控、SSL、多協議、成熟生態
理解 BIO 基礎是掌握 NIO/Netty 的前提。建議先手寫簡易 BIO 伺服器,再重構為 NIO,最後遷移到 Netty,體會每層抽象解決的核心問題。