NIO 核心組件回顧
- Channel:雙向通道(
SocketChannel、ServerSocketChannel),可非阻塞
- Buffer:數據容器(
ByteBuffer),支援讀寫模式切換
- Selector:多路復用器,單線程管理成百上千 Channel
- SelectionKey:Channel 與 Selector 註冊關係,包含就緒操作位掩碼
協議接口設計:關注點分離
public interface EchoProtocol {
void handleAccept(SelectionKey key) throws IOException;
void handleRead(SelectionKey key) throws IOException;
void handleWrite(SelectionKey key) throws IOException;
}
TCP 協議實現:狀態機驅動
public class TCPEchoSelectorProtocol implements EchoProtocol {
private final int bufSize;
public TCPEchoSelectorProtocol(int bufSize) { this.bufSize = bufSize; }
@Override
public void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel client = ssc.accept();
client.configureBlocking(false); // 必須非阻塞
// 註冊讀事件,附帶讀緩衝區
client.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
}
@Override
public void handleRead(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long read = client.read(buf);
if (read == -1) { client.close(); return; } // 對端關閉
if (read > 0) {
// 數據就緒,同時關注寫事件
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
}
@Override
public void handleWrite(SelectionKey key) throws IOException {
ByteBuffer buf = (ByteBuffer) key.attachment();
buf.flip(); // 切換讀模式
SocketChannel client = (SocketChannel) key.channel();
client.write(buf);
if (!buf.hasRemaining()) { // 寫完畢
key.interestOps(SelectionKey.OP_READ); // 只關注讀
}
buf.compact(); // 整理緩衝區,為下次讀取騰出空間
}
}
非阻塞客戶端:主動發起連接與輪詢
public class TCPEchoClientNonblocking {
public static void main(String[] args) throws Exception {
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
// 非阻塞連接:發起後輪詢 finishConnect
if (!client.connect(new InetSocketAddress("127.0.0.1", 5500))) {
while (!client.finishConnect()) { System.out.print("."); }
}
ByteBuffer writeBuf = ByteBuffer.wrap("0123456789abcdefghijklmnopqrstuvwxyz".getBytes());
ByteBuffer readBuf = ByteBuffer.allocate(writeBuf.capacity());
int total = 0;
while (total < writeBuf.capacity()) {
if (writeBuf.hasRemaining()) client.write(writeBuf);
int r = client.read(readBuf);
if (r == -1) throw new SocketException("連接提前關閉");
total += r;
System.out.print(".");
}
System.out.println("收到: " + new String(readBuf.array(), 0, total).length());
client.close();
}
}
Selector 服務端主循環
public class TCPServerSelector {
private static final int BUFSIZE = 256, TIMEOUT = 3000;
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// 註冊監聽通道
ServerSocketChannel listen = ServerSocketChannel.open();
listen.socket().bind(new InetSocketAddress(5500));
listen.configureBlocking(false);
listen.register(selector, SelectionKey.OP_ACCEPT);
EchoProtocol protocol = new TCPEchoSelectorProtocol(BUFSIZE);
while (true) {
if (selector.select(TIMEOUT) == 0) { System.out.print("."); continue; }
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next(); it.remove();
if (key.isAcceptable()) { protocol.handleAccept(key); }
else if (key.isReadable()) { protocol.handleRead(key); }
else if (key.isValid() && key.isWritable()) { protocol.handleWrite(key); }
}
}
}
}
關鍵設計要點總結
- 附件模式:用
key.attachment() 綁定 ByteBuffer,避免線程局部變量或外部 Map
- 興趣集動態調整:
key.interestOps() 實現讀寫事件的靈活切換
- 緩衝區管理:
flip() 寫入前、compact() 讀取後,保證數據不丟失、不重複
- 非阻塞連接:客戶端
connect() + finishConnect() 輪詢
- 優雅關閉:讀到
-1 時關閉 Channel,自動取消註冊
擴展方向
- 引入線程池處理業務邏輯(Reactor 多線程模型)
- 集成 Netty 等成熟框架,處理粘包/拆包、心跳、SSL 等複雜場景
- 監控指標:連接數、吞吐量、延遲分位數