天天看點

【Java.NIO】Selector,及SelectionKey

轉自:https://blog.csdn.net/robinjwong/article/details/41792623

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

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

NIO的通訊過程:

【Java.NIO】Selector,及SelectionKey

使用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()方法來實作。

  1. channel.configureBlocking(false);
  2. 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集合,
  1. int interestSet = selectionKey.interestOps();
  2. boolean isInterestedInAccept = (interestSet & Selection.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
  3. boolean isInterestedInConnect = interestSet & SelectioKey.OP_CONNECT;
  4. boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
  5. boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
  • ready集合 —— 是通道已經準備就緒的操作的集合,在一個選擇後,你會是首先通路這個ready set,
int readySet = selectionKey.readyOps();           
可以向檢測interet集合那樣的方法,來檢測channel中什麼事件或操作已經就緒,也可以使用一下四個方法,
  1. selectionKey.isAcceptable();
  2. selectionKey.isConnectable();
  3. selectionKey.isReadable();
  4. selectionKey.isWritable();
  • 從SelectionKey方位Channel和Selector:
  1. Channel channel = selectionKey.channel();
  2. Selector selector = selectionKey.selector();
  • 附加的對象 —— 可以将一個對象或者更多的資訊附着到SelectionKey上,這樣就能友善的識别某個給定的通道。例如,可以附加與通道一起使用的Buffer,或是包含聚集資料的某個對象,
  1. selectionKey.attach(theObject);
  2. 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();           

可以周遊這個已選擇的集合來通路就緒的通道:

  1. Set selectedKeys = selector.selectedKeys();
  2. Iterator keyIterator = selectedKeys.iterator();
  3. while(keyIterator.hasNext()){
  4. SelectionKey key = keyIterator.next();
  5. if (key.isAcceptable()){ // a connection was accepted by a ServerSocketChannel
  6. }else
  7. if (key.isConnectable()){ // a connection was eatablished with a remote server
  8. }else
  9. if (key.isReadable()){ // a channel is ready for reading
  10. }else
  11. if (key.isWritable()){ // a channel is ready for writing
  12. }
  13. keyIterator.remove();
  14. }

這個循環周遊已選擇集中的每個鍵,并檢測各個鍵所對象的通道的就緒事件。

注意每次疊代末尾的remove()調用,Selector不會自己從已選擇集中移除SelectioKey執行個體,必須在處理完通道時自己移除。

Selector的wakeUp()方法

某個線程調用select()方法後阻塞了,即使沒有通道已經就緒,也有辦法讓其從select()方法傳回。隻要讓其他線程在第一個線程調用select()方法的那個對象上調用Selector.wakeup()方法即可。阻塞在select()方法上的線程會立馬傳回。

一個示例

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

  1. Selector selector = Selector.open();
  2. channel.configureBlocking(false);
  3. SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
  4. while(true) {
  5. int readyChannels = selector.select();
  6. if(readyChannels == 0) continue;
  7. Set selectedKeys = selector.selectedKeys();
  8. Iterator keyIterator = selectedKeys.iterator();
  9. while(keyIterator.hasNext()) {
  10. SelectionKey key = keyIterator.next();
  11. if(key.isAcceptable()) {
  12. // a connection was accepted by a ServerSocketChannel.
  13. } else if (key.isConnectable()) {
  14. // a connection was established with a remote server.
  15. } else if (key.isReadable()) {
  16. // a channel is ready for reading
  17. } else if (key.isWritable()) {
  18. // a channel is ready for writing
  19. }
  20. keyIterator.remove();
  21. }
  22. }