首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java NIO 核心原理與客戶端/服務端完整代碼實現

     編輯:Java 相關知識

Java NIO 非阻塞 I/O 深度解析與實戰

Java NIO (New I/O / Non-blocking I/O) 自 JDK 1.4 引入,徹底改變了網路編程模型。本文從阻塞 I/O 缺陷切入,詳細闡述 NIO 的 Reactor 模型、Selector/Channel/Buffer 核心組件,並提供完整的服務端與客戶端代碼實現。

阻塞 I/O (BIO) 的核心缺陷

  1. 線程模型局限:每連接一線程,高併發下線程數暴增,記憶體佔用大(棧空間)、上下文切換頻繁
  2. 資源浪費:大量線程處於 read()/accept() 阻塞等待,CPU 利用率低

NIO 核心組件與 Reactor 模型

組件角色
Channel雙向通道,替代單向 Stream,可註冊事件
Buffer資料緩衝區,讀寫翻轉模式
Selector多路複用器,單線程監控多 Channel 事件
SelectionKeyChannel 與 Selector 註冊關係,包含就緒事件集合

四大 SelectionKey 事件類型

  • OP_ACCEPT (16):服務端接受客戶端連接
  • OP_CONNECT (8):客戶端連接服務端完成
  • OP_READ (1):讀就緒
  • OP_WRITE (4):寫就緒

NIO 服務端完整實現

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();
    }
}

NIO 客戶端完整實現

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();
    }
}

關鍵技術點總結

  1. 非阻塞連接:connect() 發起後需在 OP_CONNECT 事件中呼叫 finishConnect()
  2. Buffer 翻轉:寫入後 flip() 切換讀模式,讀取後 clear() 或 compact() 重置
  3. 事件驅動:單線程輪詢 selector.select(),僅處理就緒事件,避免無效等待
  4. SelectionKey 管理:處理完必須 ite.remove(),否則下次輪詢重複觸發

NIO 適用場景與局限

  • 適用:高併發、長連接、IO 密集型服務(如即時通訊、遊戲伺服器)
  • 局限:編程複雜度高、業務邏輯複雜時回調地獄、CPU 密集型任務不適用
  • 演進:Netty 框架封裝 NIO 細節,提供高階 API,生產環境建議直接使用 Netty

理解 NIO 底層原理有助於排查 Netty 等框架的疑難雜症,也是掌握高效能網路編程的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved