Java NIO (New I/O / Non-blocking I/O) 自 JDK 1.4 引入,徹底改變了網路編程模型。本文從阻塞 I/O 缺陷切入,詳細闡述 NIO 的 Reactor 模型、Selector/Channel/Buffer 核心組件,並提供完整的服務端與客戶端代碼實現。
read()/accept() 阻塞等待,CPU 利用率低| 組件 | 角色 |
|---|---|
| Channel | 雙向通道,替代單向 Stream,可註冊事件 |
| Buffer | 資料緩衝區,讀寫翻轉模式 |
| Selector | 多路複用器,單線程監控多 Channel 事件 |
| SelectionKey | Channel 與 Selector 註冊關係,包含就緒事件集合 |
OP_ACCEPT (16):服務端接受客戶端連接OP_CONNECT (8):客戶端連接服務端完成OP_READ (1):讀就緒OP_WRITE (4):寫就緒public class NIOServer {
private Selector selector;
public void initServer(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 非阻塞模式
serverChannel.socket().bind(new InetSocketAddress(port));
this.selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException {
System.out.println("服務端啟動成功!");
while (true) {
selector.select(); // 阻塞等待事件
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = ite.next();
ite.remove(); // 必須移除,防止重複處理
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.write(ByteBuffer.wrap("歡迎連接 NIO 服務端".getBytes()));
channel.register(selector, SelectionKey.OP_READ); // 註冊讀事件
} else if (key.isReadable()) {
read(key);
}
}
}
}
public void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer);
if (len > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String msg = new String(data).trim();
System.out.println("收到訊息:" + msg);
channel.write(ByteBuffer.wrap(("Echo: " + msg).getBytes()));
} else if (len == -1) {
channel.close(); // 客戶端斷開
}
}
public static void main(String[] args) throws IOException {
new NIOServer().initServer(8000).listen();
}
}
public class NIOClient {
private Selector selector;
public void initClient(String ip, int port) throws IOException {
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
this.selector = Selector.open();
channel.connect(new InetSocketAddress(ip, port));
channel.register(selector, SelectionKey.OP_CONNECT);
}
public void listen() throws IOException {
while (true) {
selector.select();
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = ite.next();
ite.remove();
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.isConnectionPending()) {
channel.finishConnect(); // 完成非阻塞連接
}
channel.configureBlocking(false);
channel.write(ByteBuffer.wrap("客戶端發送測試訊息".getBytes()));
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
read(key); // 邏輯同服務端
}
}
}
}
public static void main(String[] args) throws IOException {
new NIOClient().initClient("localhost", 8000).listen();
}
}
connect() 發起後需在 OP_CONNECT 事件中呼叫 finishConnect()flip() 切換讀模式,讀取後 clear() 或 compact() 重置selector.select(),僅處理就緒事件,避免無效等待ite.remove(),否則下次輪詢重複觸發理解 NIO 底層原理有助於排查 Netty 等框架的疑難雜症,也是掌握高效能網路編程的基石。