【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成功連線到另一個伺服器稱為”連線就緒
這四種事件用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();
}
}