Java NIO 通過 Selector 實現單線程多路復用,是 Reactor 模式在 Java 的標準實現。本文從 NIO 核心組件入手,構建完整 Reactor 服務端,演進至多 Reactor 多線程架構,揭示 Netty 等高性能框架的設計淵源。
| 組件 | 角色 | 關鍵方法 |
|---|---|---|
Selector | 多路復用器,監聽多 Channel 事件 | select(), selectedKeys(), wakeup() |
Channel | 雙向數據通道,非阻塞模式 | register(), read(), write(), accept() |
SelectionKey | Channel-Registrar 綁定標識,攜帶附件 | interestOps(), attachment(), channel() |
ByteBuffer | 數據容器,支援直接內存 | flip(), compact(), clear(), get()/put() |
public class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocket;
Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
serverSocket.configureBlocking(false);
// 註冊 ACCEPT 事件,附件綁定 Acceptor Handler
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor());
}
public void run() {
try {
while (!Thread.interrupted()) {
selector.select(); // 阻塞等待事件
Set<SelectionKey> selected = selector.selectedKeys();
for (SelectionKey key : selected) {
dispatch(key); // 分發到 Handler
}
selected.clear();
}
} catch (IOException ex) { /* handle */ }
}
void dispatch(SelectionKey key) {
Runnable handler = (Runnable) key.attachment();
if (handler != null) handler.run();
}
}
class Acceptor implements Runnable {
public void run() {
try {
SocketChannel client = serverSocket.accept();
if (client != null) {
// 創建讀寫 Handler,註冊 READ 事件
new SocketReadHandler(selector, client);
}
} catch (IOException ex) { /* handle */ }
}
}
class SocketReadHandler implements Runnable {
final SocketChannel socket;
final SelectionKey sk;
static final int READING = 0, SENDING = 1;
int state = READING;
SocketReadHandler(Selector sel, SocketChannel c) throws IOException {
socket = c;
socket.configureBlocking(false);
sk = socket.register(sel, 0); // 初始不關注事件
sk.attach(this); // 關鍵:附件綁定自己
sk.interestOps(SelectionKey.OP_READ); // 關注讀事件
sel.wakeup(); // 喚醒 selector.select()
}
public void run() {
try {
if (state == READING) readRequest();
else if (state == SENDING) sendResponse();
} catch (IOException ex) { /* 關閉連接 */ }
}
void readRequest() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = socket.read(buffer);
if (bytesRead > 0) {
buffer.flip();
// 解碼請求 -> 業務處理 (丟入線程池) -> 編碼響應
// 狀態轉換
state = SENDING;
sk.interestOps(SelectionKey.OP_WRITE);
} else if (bytesRead == -1) {
socket.close(); // 對端關閉
}
}
void sendResponse() throws IOException {
// 寫入響應緩衝區
// 寫完後
state = READING;
sk.interestOps(SelectionKey.OP_READ);
}
}
SelectionKey.attach(Handler) 實現 Channel 與 Handler 雙向綁定,事件觸發 O(1) 獲取 Handlerstate 控制關注事件切換 (READ ↔ WRITE),避免忙輪詢interestOps() 按需開啟/關閉 OP_READ/OP_WRITE
// MainReactor (1 線程) - 只負責 ACCEPT
Selector mainSelector = Selector.open();
serverSocket.register(mainSelector, OP_ACCEPT, mainAcceptorHandler);
// SubReactor 池 (N 線程) - 負責 READ/WRITE/業務分發
Selector[] subSelectors = new Selector[Runtime.getRuntime().availableProcessors()];
ExecutorService workerPool = Executors.newFixedThreadPool(16);
// Acceptor 中輪詢分配 SubReactor
SocketChannel client = serverSocket.accept();
SubReactor nextSub = subReactors[nextIndex++ % subSelectors.length];
nextSub.registerChannel(client); // 跨線程註冊需 wakeup()
| 本文組件 | Netty 對應 |
|---|---|
| MainReactor | NioEventLoopGroup bossGroup |
| SubReactor | NioEventLoopGroup workerGroup |
| Handler | ChannelHandler / ChannelPipeline |
| ByteBuffer | ByteBuf (引用計數、池化、零拷貝) |
| wakeup() | NioEventLoop.register() 內部喚醒 |
ByteBuffer.allocateDirect() 減少堆外拷貝PooledByteBufAllocatorFileChannel.transferTo() / mmapselector.selectNow() 非阻塞輪詢 + 批量分發Channel.config().setAutoRead(false) 手動控制讀取節奏Java NIO Selector + Reactor 模式是高性能網絡服務的基石。從單線程 Demo 到主從 Reactor 多線程,再到 Netty 産業級實現,核心思想始終是:**事件驅動、非阻塞 I/O、線程模型解耦、零拷貝優化**。掌握 Reactor 核心流程與關鍵優化點,即可讀懂 Netty 源碼、排查疑難性能問題、設計自定義協議框架。