1. 程式人生 > >NIO學習筆記--Selector選擇器基礎

NIO學習筆記--Selector選擇器基礎

Selector是Java NIO中檢測一個或多個Channel的,同時確定哪些channel是否已經可以進行讀或者寫的元件,這樣一個執行緒就可以管理多個channel,從而管理多個網路連線,

1.Selector概述:

Selector的優點:可以使用更少執行緒來管理channel(執行緒會佔用記憶體資源,執行緒的切換對系統的開銷也很大,一般執行緒越少越好,但隨著現在機器效能提高,多核計算使用單執行緒反而浪費資源,),理論上可以由一個執行緒管理所有channel.

使用Selector.open()建立一個Selector

2.向Selector註冊Channel

Channel和Selector要一起使用就必須將Channel註冊到一個Selector上,和selector一起使用,channel必須時未阻塞的,那麼由於FileChannel不能切換為非阻塞模式,FileChannel不能和selector一起使用,而Socket channel可以;注意register的第二個引數,這是一個interest set,channel通過它監聽channel感興趣的事件,有以下4件事情可以監聽:1.Connect,2.accept 3.read 4.write

//調整channel的阻塞狀態,false為非阻塞
channel.configureBlocking(false);
//向給定的選擇器註冊此通道,返回一個選擇鍵
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

一個channel註冊一個事件也可以說是該事件已經準備好了,所以一個channel連線到一個伺服器就是connect ready連線就緒,一個server socket channel接受新進入的連線就是accept ready接受就緒.一個channel有資料準備讀取,就是read ready 讀取就緒,一個channel可以寫資料就是write reay寫入就緒;

這四個時間由四個selectionKey常量表示:

1. SelectionKey.OP_CONNECT, 2. SelectionKey.OP_ACCEPT,3. SelectionKey.OP_READ,4. SelectionKey.OP_WRITE

如果不知對一種時間感興趣,可以使用"位或"操作符將常量連線起來

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey選擇鍵

register註冊方法會返回一個SelectionKey,表示SelectableChannel在Selector中註冊的標記,在呼叫鍵的cancel方法,關閉其channel或者關閉SelectionKey來取消這個鍵之前,會一直有效.取消某個鍵不會立即在其選擇器彙總移除它,會將該鍵新增到選擇氣的已取消鍵集,,以便在下一次進行選擇操作時移除它,可通過呼叫isValid方法來測試其有效性,這個鍵物件包含一些屬性:

1. interest set       2. ready set      3. Channel       4. Selector        5.An attached object 附加物件(可選)

Interest Set

是Channel向Selector註冊時選擇額感興趣的時間的合集,可以通過SelectionKey來讀寫interest:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE; 

和上面的官網文件demo一樣可以通過邏輯與來判斷某個事件是否在Interest Set中存在

Ready Set

ready set 是channel已經準備好的操作的集合,在一次selection後你要首先訪問這個ready set,Selection使用方法:

int readySet = selectionKey.readyOps();

可以使用和上面interest Set一樣的方法檢測什麼樣的事件已經準備好了,但你也能用以下的方法來檢測

selectionKey.isAcceptable() ; selectionKey.isConnectable() ; selectionKey.isReadable() ; selectionKey.isWritable() ;

Channel + Selector

使用SelectionKey來訪問channel+selector是簡單地:

Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();

Attaching Objects

你可以附加一個物件或更多資訊給一個SelectionKey,這是一個方便識別給定channel的方法,比如,你可以在使用channel時附加buffer或一個包含更多資料集合的物件,或者可以在channel註冊到selector方法時也可以附加一個物件

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment;
SelectionKey key = channel.register(selector,SelectionKey.OP_READ,theObject);

Selecting Channels via a Selector 通過Selector選擇channels

一旦一個或多個channels註冊到selector,就可以呼叫select()方法,這些方法可返回對你所感興趣的事件已經準備好的哪些通道,換句話說,如果你對某個事件感興趣,這個方法可以返回這個事件已經就緒的channels;

select()方法:

 int select(): 阻塞直到至少一個channel在你註冊的事件上就緒了;

int select(long timeout) ;在select()基礎上有個最長阻塞時間;

 int selectNow(): 不會阻塞,無論什麼通道只要就緒立即返回(網路摘錄:此方法執行非阻塞的選擇操作。如果自從前一次選擇操作後,沒有通道變成可選擇的,則此方法直接返回零。)

這三個方法返回的int表示準備就緒的channel數量,注意:這個返回值是上次select()方法後有多少channel準備就緒,即使上一次select()出的channel並沒有進行任何操作

selectedKeys()

一旦呼叫一次select()方法,同時返回值表示至少一個channel就緒,你可以通過呼叫selector的selectedKeys()方法訪問selected key set(已選擇鍵集)中的這個就緒的channel

Set<SelectionKey> selectedKeys = selector.selectedKeys

當使用channel.register()方法將channel註冊個一個selector返回的selectionKey物件,它表示了那個channel註冊the selector上,你可以通過SelectionKey的selectedKeySet()方法訪問者這些鍵,可以遍歷這個selected key set來訪問這些就緒的channel

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
}

這個迴圈對selected key set進行遍歷,並對每一個key檢測它對應的channel的就緒事件.

keyIterator.remove()這個方法在每次迭代結尾呼叫,這個Selector不會自己移除這個SelectionKey例項,必須要在完成通道加工後自己刪除,下一次channel就緒時這個selector會再次將他放入selected key set;

SelectionKey.channel()方法返回的channel 需要轉換成需要處理的型別;

wakeUp()

一個執行緒呼叫select()方法後阻塞了 ,即使沒有channel就緒,也可以從select()方法離開,這是通過其他執行緒在第一個執行緒呼叫select()方法的selector物件上呼叫Selector.wakeup()方法實現的.阻塞在select()方法上的執行緒會立即返回.

如果一個其他的執行緒呼叫wakeup()方法但當前沒有執行緒在select()方法上阻塞,下一個呼叫select()方法的執行緒會立刻被wakeup喚醒

close()

使用完selector後呼叫close()方法關閉它,關閉selector會使註冊到這個selector的所有selectionkey例項失效,channel本身不會關閉