1. 程式人生 > >【Java.NIO】Selector,及SelectionKey

【Java.NIO】Selector,及SelectionKey

java.nio.channels
public abstract class Selector extends Object implements Closeable

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否為諸如讀寫事件做好準備的元件。這樣,一個單獨的執行緒可以管理多個channel,從而管理多個網路連線

NIO的通訊過程:


使用Selector

僅用單個執行緒來處理多個Channels的好處是,只需要更少的新城來處理通道。事實上,可以只用一個執行緒處理所有的通道。

Selector的建立

通過呼叫Selector.open()方法建立一個Selector,

Selector selector = Selector.open();

  • isOpen() —— 判斷Selector是否處於開啟狀態。Selector物件建立後就處於開啟狀態了
  • close() —— 當呼叫了Selector物件的close()方法,就進入關閉狀態.。用完Selector後呼叫其close()方法會關閉該Selector,且使註冊到該Selector上的所有SelectionKey例項無效。通道本身並不會關閉

向Selector註冊通道

為了將Channel和Selector配合使用,必須將channel註冊到selector上。

通過SelectableChannel。register()方法來實現。

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

與Selector一起使用時,Channel必須處於非阻塞模式下。

這意味著FIleChannel與Selector不能一起使用。

注意register()方法的第二個引數,這是一個”interest集合“,意思是在通過Selector監聽Channel時對什麼事件感興趣。

可以監聽四種不同型別的事件:

  • Connect
  • Accept
  • Read
  • Write

通道觸發了一個事件意思是該事件已經就緒。所以,某個channel成功連線到另一個伺服器稱為”連線就緒

“。一個server socket channel準備號接收新進入的連線稱為”接收就緒“。一個有資料可讀的通道可以說是”讀就緒“。等代寫資料的通道可以說是”寫就緒“。

這四種事件用SelectionKey的四個常量來表示:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

register()返回值 —— SelectionKey,  Selector中的SelectionKey集合

只要ServerSocketChannel及SocketChannel向Selector註冊了特定的事件,Selector就會監控這些事件是否發生

SelectableChannel的register()方法返回一個SelectionKey物件,該物件是用於跟蹤這些被註冊事件的控制代碼

一個Selector物件會包含3種類型的SelectionKey集合:

  • all-keys集合 —— 當前所有向Selector註冊的SelectionKey的集合,Selector的keys()方法返回該集合
  • selected-keys集合 —— 相關事件已經被Selector捕獲的SelectionKey的集合,Selector的selectedKeys()方法返回該集合
  • cancelled-keys集合 —— 已經被取消的SelectionKey的集合,Selector沒有提供訪問這種集合的方法

當register()方法執行時,新建一個SelectioKey,並把它加入Selector的all-keys集合中。

如果關閉了與SelectionKey物件關聯的Channel物件,或者呼叫了SelectionKey物件的cancel方法,這個SelectionKey物件就會被加入到cancelled-keys集合中,表示這個SelectionKey物件已經被取消。

在執行Selector的select()方法時,如果與SelectionKey相關的事件發生了,這個SelectionKey就被加入到selected-keys集合中,程式直接呼叫selected-keys集合的remove()犯法,或者呼叫它的iterator的remove()方法,都可以從selected-keys集合中刪除一個SelectionKey物件。

SelectionKey

表示SelectableChannel 在 Selector 中的註冊的標記/控制代碼。

register()方法返回一個SelectinKey物件,這個物件包含一些你感興趣的屬性:

  • interest集合
  • ready集合
  • Channel
  • Selector
  • 附加的物件

通過呼叫某個SelectionKey的cancel()方法,關閉其通道,或者通過關閉其選擇器來取消該Key之前,它一直保持有效。

取消某個Key之後不會立即從Selector中移除它,相反,會將該Key新增到Selector的已取消key set,以便在下一次進行選擇操作的時候移除它。

  • interest集合 —— 感興趣的事件集合,可以通過SelectionKey讀寫interest集合,
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & Selection.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectioKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

  • ready集合 —— 是通道已經準備就緒的操作的集合,在一個選擇後,你會是首先訪問這個ready set,
int readySet = selectionKey.readyOps();

可以向檢測interet集合那樣的方法,來檢測channel中什麼事件或操作已經就緒,也可以使用一下四個方法,

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

  • 從SelectionKey方位Channel和Selector:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();

  • 附加的物件 —— 可以將一個物件或者更多的資訊附著到SelectionKey上,這樣就能方便的識別某個給定的通道。例如,可以附加與通道一起使用的Buffer,或是包含聚集資料的某個物件,
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();


通過Selector選擇就緒的通道

一旦向Selector註冊了一個或多個通道,就可以呼叫幾個過載的select()方法。

這些方法返回你所感興趣的事件(連線,接受,讀或寫)已經準備就緒的那些通道。換句話說,如果你對”讀就緒“的通道感興趣,select()方法會返回讀事件已經就緒的那些通道。

  • select() —— 阻塞到至少有一個通道在你註冊的事件上就緒了
  • select(long timeout) —— 和select()一樣,除了最長會阻塞timeout毫秒
  • selectNow() —— 不會阻塞,不管什麼通道就緒都立刻返回;此方法執行非阻塞的選擇操作,如果自從上一次選擇操作後,沒有通道變成可選擇的,則此方法直接返回0
  • select()方法返回的Int值表示多少通道就緒。

一旦呼叫了select()方法,並且返回值表明有一個或更多個通道就緒了,然後可以通過呼叫selector的selectorKeys()方法,訪問”已選擇鍵集“中的就緒通道

Set selectedKeys = selector.selectedKeys();

可以遍歷這個已選擇的集合來訪問就緒的通道

Set selectedKeys = selector.selectedKeys();
Iterator 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 eatablished 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();
}

這個迴圈遍歷已選擇集中的每個鍵,並檢測各個鍵所物件的通道的就緒事件。

注意每次迭代末尾的remove()呼叫,Selector不會自己從已選擇集中移除SelectioKey例項,必須在處理完通道時自己移除

Selector的wakeUp()方法

某個執行緒呼叫select()方法後阻塞了,即使沒有通道已經就緒,也有辦法讓其從select()方法返回。只要讓其他執行緒在第一個執行緒呼叫select()方法的那個物件上呼叫Selector.wakeup()方法即可。阻塞在select()方法上的執行緒會立馬返回。

一個示例

開啟一個Selector,註冊一個通道註冊到這個Selector上,然後持續監控這個Selector的四中事件是否就緒:

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;

  Set selectedKeys = selector.selectedKeys();
  Iterator 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();
  }
}