| 維度 | 傳統 BIO (Blocking I/O) | NIO (Non-blocking I/O) |
|---|---|---|
| 執行緒模型 | 一連接一執行緒 | 少量執行緒輪詢多通道 |
| 擴展性 | 受限於執行緒數(千級瓶頸) | 單機輕鬆支撐 10K+ 併發 |
| 資源開銷 | 執行緒棧記憶體、上下文切換大 | 事件驅動,無閒置執行緒等待 |
| 程式複雜度 | 線性邏輯,簡單直觀 | 狀態機/事件回調,較複雜 |
SocketChannel、ServerSocketChannel)。position、limit、capacity,支援 flip()、clear()、compact()。OP_ACCEPT、OP_READ、OP_WRITE)。import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
public class NioEchoServer {
private static final int PORT = 8080;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) throws Exception {
// 1. 開啟 ServerSocketChannel 並設為非阻塞
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// 2. 綁定埠口
ServerSocket serverSocket = serverChannel.socket();
serverSocket.bind(new InetSocketAddress(PORT));
// 3. 建立 Selector 並註冊 ACCEPT 事件
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Echo Server listening on port " + PORT);
ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
// 4. 事件迴圈
while (true) {
int ready = selector.select(); // 阻塞直到有就緒事件
if (ready == 0) continue;
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // 必須移除,避免重複處理
if (key.isAcceptable()) {
handleAccept(key, selector);
} else if (key.isReadable()) {
handleRead(key, buffer);
}
}
}
}
private static void handleAccept(SelectionKey key, Selector selector) throws Exception {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 註冊讀事件,附加歡迎訊息作為附件
client.register(selector, SelectionKey.OP_READ,
"Welcome! Type something, I'll echo it back.\n[echo] ".getBytes());
}
private static void handleRead(SelectionKey key, ByteBuffer buffer) throws Exception {
SocketChannel client = (SocketChannel) key.channel();
buffer.clear();
int bytesRead = client.read(buffer);
if (bytesRead == -1) { // 客戶端正常關閉
client.close();
return;
}
buffer.flip();
// 簡單回顯邏輯:收到換行符前綴 [echo]
while (buffer.hasRemaining()) {
if (buffer.get() == '\r') { // 簡易示範,實際需處理 \r\n
buffer.position(buffer.position() - 1); // 回退
ByteBuffer prefix = ByteBuffer.wrap("[echo] ".getBytes());
// 這裡為演示簡化,實際應用需組裝完整回應 Buffer
}
}
// 回寫資料(實際專案需處理部分寫入、註冊 OP_WRITE)
buffer.flip();
client.write(buffer);
// 重置附件供下次讀取
key.attach(null);
}
}
ByteBuffer.allocateDirect() 分配堆外記憶體,減少 JVM 與 OS 間資料拷貝,提升 I/O 效能。selector.select() 阻塞等待,就緒後遍歷 selectedKeys(),每個 Key 處理完必須 it.remove()。key.attach(obj) 儲存連線上下文(如會話狀態、未發送完的緩衝區),避免額外 Map 維護。LengthFieldBasedFrameDecoder)或自定義協議幀處理。client.write() 可能部分寫入,需檢查 buffer.hasRemaining(),並註冊 OP_WRITE 待可寫時繼續。手寫 NIO 伺服器教學價值高,但生產環境強烈建議使用 Netty、Mina 等成熟框架,它們解決了:
Java NIO 以 Selector 多路複用、Channel 非阻塞、Buffer 零拷貝三大核心,重新定義了 Java 網路程式設計效能上限。掌握 NIO 原理不僅能讀懂 Netty 源碼,更能在面試與架構設計中遊刃有餘。