Java NIO (New I/O) 引入了通道與緩衝區模型,配合 Selector 多路復用機制,實現了單線程高效管理成千上萬連接的能力。本文深入剖析 Selector 工作流程、關鍵 API,並提供一個完整的非阻塞 TCP 服務端示例,幫助開發者快速掌握 NIO 核心編程範式。
ServerSocketChannel、SocketChannel,可配置為非阻塞模式。Selector.open()。ServerSocketChannel,綁定端口,設置非阻塞 configureBlocking(false)。channel.register(selector, SelectionKey.OP_ACCEPT)。selector.select() 阻塞等待事件就緒。selector.selectedKeys(),遍歷處理:while (true) {
int ready = selector.select(); // 阻塞直到至少一個通道就緒
if (ready == 0) continue;
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // 必須移除,避免重複處理
if (key.isAcceptable()) {
// 接受新連接
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 註冊讀/寫事件
sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} else if (key.isReadable()) {
// 讀取數據
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = sc.read(buf);
if (len > 0) {
buf.flip();
// 處理業務邏輯...
} else if (len == -1) {
key.cancel();
sc.close();
}
} else if (key.isWritable()) {
// 寫入數據邏輯
}
}
}
public class NioServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Server started on port 8080");
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
read(key);
}
}
}
}
private static void accept(SelectionKey key) throws IOException {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("Client connected: " + sc.getRemoteAddress());
}
private static void read(SelectionKey key) throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
buf.clear();
int len = sc.read(buf);
if (len > 0) {
buf.flip();
String msg = StandardCharsets.UTF_8.decode(buf).toString();
System.out.println("Received: " + msg.trim());
// 回傳確認
sc.write(StandardCharsets.UTF_8.encode("ACK: " + msg));
} else if (len == -1) {
key.cancel();
sc.close();
System.out.println("Client disconnected");
}
}
}
configureBlocking(false) 才能註冊到 Selector。it.remove(),否則下次 select 仍會返回已處理的鍵,導致死循環或重複處理。ByteBuffer 作為 attachment,避免頻繁分配內存。OP_WRITE 常時就緒,建議僅在發送緩衝區有數據待發送時註冊,發送完畢後立即取消註冊,避免 CPU 空轉。| 維度 | BIO (阻塞 I/O) | NIO (非阻塞 I/O) |
|---|---|---|
| 線程模型 | 一連接一線程 | 單線程多路復用 |
| 併發擴展性 | 受限於線程數 | 支援萬級連接 (C10K) |
| 編程複雜度 | 簡單直觀 | 較高 (狀態機、緩衝區管理) |
| 適用場景 | 連接數少、邏輯簡單 | 高併發、長連接、即時通訊 |
Java NIO 的 Selector 機制是高性能網絡編程的基石。理解「註冊 -> 輪詢 -> 處理」三步曲,熟練掌握 SelectionKey 狀態流轉與 ByteBuffer 讀寫技巧,即可構建出穩定高效的網絡服務端。實際項目中,推薦使用 Netty 等成熟框架屏蔽底層細節,專注業務邏輯開發。