NIO詳細介紹(Selector,Channel)

1.Selector(多路複用)

原先的bio中,一個客戶端連接,就爲它分配一個線程。這樣的問題,當用戶激增時候,線程會增加很多,增加服務器開銷。

所以後來使用了線程池進行管理線程,但是有個弊端,如果線程池有100個線程,這個時候第101個就會等待。傳統的bio(Server/Client)如下圖:

有這個弊端,Nio就用selector解決。

NIO中非阻塞I/O 採用了基於Reactor模式的工作方式,I/O 調用不會被阻塞,相反是註冊感興趣的特定I/O 事件,如可讀數據到
達,新的套接字連接等等,在發生特定事件時,系統再通知我們。NIO中實現非阻塞I/O的核心對象就是Selector,Selector 就是
註冊各種I/O 事件地方,而且當那些事件發生時,就是這個對象告訴我們所發生的事件,如下圖所示:

從圖中可以看出,當有讀或寫等任何註冊的事件發生時,可以從Selector 中獲得相應的SelectionKey,同時從 SelectionKey中可
以找到發生的事件和該事件所發生的具體的SelectableChannel,以獲得客戶端發送過來的數據。
 

使用NIO中非阻塞I/O 編寫服務器處理程序,大體上可以分爲下面三個步驟:
1. 向Selector 對象註冊感興趣的事件。
2. 從Selector 中獲取感興趣的事件。
3. 根據不同的事件進行相應的處理。
/* * 註冊事件 */

private Selector getSelector() throws IOException {

// 創建 Selector 對象

Selector sel = Selector.open();
// 創建可選擇通道,並配置爲非阻塞模式

ServerSocketChannel server = ServerSocketChannel.open();

server.configureBlocking(false);
// 綁定通道到指定端口

ServerSocket socket = server.socket();

InetSocketAddress address = new InetSocketAddress(port);

socket.bind(address);
// 向 Selector 中註冊感興趣的事件

server.register(sel, SelectionKey.OP_ACCEPT); return sel;
}

創建了ServerSocketChannel對象,並調用 configureBlocking()方法,配置爲非阻塞模式,接下來的三行代碼把該通道綁定到指定端口,最後向Selector 中註冊事件,此處指定的是參數是OP_ACCEPT,即指定我們想要監聽accept 事件,也就是新的連接發 生時所產生的事件,對於ServerSocketChannel 通道來說,我們唯一可以指定的參數就是OP_ACCEPT。

當Selector 中獲取感興趣的事件,即開始監聽,進入內部循環:

public void listen(){
    System.out.println("listen on " + this.port + ".");
    try {
        //輪詢主線程

        while (true){
            //大堂經理再叫號
            selector.select();
            //每次都拿到所有的號子
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iter = keys.iterator();
            //不斷地迭代,就叫輪詢
            //同步體現在這裏,因爲每次只能拿一個key,每次只能處理一種狀態
            while (iter.hasNext()){
                SelectionKey key = iter.next();
                iter.remove();
                //每一個key代表一種狀態
                //沒一個號對應一個業務
                //數據就緒、數據可讀、數據可寫 等等等等
                process(key);
            }
            
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

在非阻塞I/O 中,內部循環模式基本都是遵循這種方式。首先調用select()方法,該方法會阻塞,直到至少有一個事件發生,然後
再使用selectedKeys()方法獲取發生事件的SelectionKey,再使用迭代器進行循環。

最後根據不同事件進行不同處理:

private void process(SelectionKey key) throws IOException {
    //針對於每一種狀態給一個反應
    if(key.isAcceptable()){
        ServerSocketChannel server = (ServerSocketChannel)key.channel();
        //這個方法體現非阻塞,不管你數據有沒有準備好
        //你給我一個狀態和反饋
        SocketChannel channel = server.accept();
        //一定一定要記得設置爲非阻塞
        channel.configureBlocking(false);
        //當數據準備就緒的時候,將狀態改爲可讀
        key = channel.register(selector,SelectionKey.OP_READ);
    }
    else if(key.isReadable()){
        //key.channel 從多路複用器中拿到客戶端的引用
        SocketChannel channel = (SocketChannel)key.channel();
        int len = channel.read(buffer);
        if(len > 0){
            buffer.flip();
            String content = new String(buffer.array(),0,len);
            key = channel.register(selector,SelectionKey.OP_WRITE);
            //在key上攜帶一個附件,一會再寫出去
            key.attach(content);
            System.out.println("讀取內容:" + content);
        }
    }
    else if(key.isWritable()){
        SocketChannel channel = (SocketChannel)key.channel();

        String content = (String)key.attachment();
        channel.write(ByteBuffer.wrap(("輸出:" + content).getBytes()));

        channel.close();
    }
}

2.Channel

通道是一個對象。我們用來讀取和輸出對象。裏面的數據我們不是用bio中的字節流處理,而是用buffer緩衝區。是將數據從通
道讀入緩衝區,再從緩衝區獲取這個字節。

在NIO 中,提供了多種通道對象,而所有的通道對象都實現了 Channel 接口。它們之間的繼承關係如下圖所示:

 

 

 

 

 

發佈了8 篇原創文章 · 獲贊 4 · 訪問量 1370
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章