Selector(選擇器)可同時監聽多個通道上感興趣的事件(如accept事件、read就緒事件、write就緒事件)。使用Selector可以實作一個線程管理多個通道,進而可以管理多個連接配接。
1.為什麼要使用Selector
如果不使用Selector要監聽多個channel上感興趣的事件,則需要多線程操作,一個線程監聽一個通道的事件。這樣導緻線程上下文切換的開銷(記憶體)、增加了程式設計的複雜度。
2.Selector的建立、注冊
(1)建立
Selector selector = Selector.open();
(2)注冊
建立了Selector之後,需要将需要監聽的通道以及該通道上感興趣的事件注冊在selector上。
其中感興趣的事件包括一下4種:
SelectionKey中定義的常量:
/**
* Operation-set bit for read operations.
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_READ</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding channel is ready for reading, has reached
* end-of-stream, has been remotely shut down for further reading, or has
* an error pending, then it will add <tt>OP_READ</tt> to the key's
* ready-operation set and add the key to its selected-key set. </p>
*/
public static final int OP_READ = << ;
/**
* Operation-set bit for write operations. </p>
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_WRITE</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding channel is ready for writing, has been
* remotely shut down for further writing, or has an error pending, then it
* will add <tt>OP_WRITE</tt> to the key's ready set and add the key to its
* selected-key set. </p>
*/
public static final int OP_WRITE = << ;
/**
* Operation-set bit for socket-connect operations. </p>
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_CONNECT</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding socket channel is ready to complete its
* connection sequence, or has an error pending, then it will add
* <tt>OP_CONNECT</tt> to the key's ready set and add the key to its
* selected-key set. </p>
*/
public static final int OP_CONNECT = << ;
/**
* Operation-set bit for socket-accept operations. </p>
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_ACCEPT</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding server-socket channel is ready to accept
* another connection, or has an error pending, then it will add
* <tt>OP_ACCEPT</tt> to the key's ready set and add the key to its
* selected-key set. </p>
*/
public static final int OP_ACCEPT = << ;
SelectionKey.OP_READ表示關注讀資料就緒事件
SelectionKey.OP_WRITE表示關注寫資料就緒事件
SelectionKey.OP_CONNECT表示關注socket channel的連接配接完成事件
SelectionKey.OP_ACCEPT表示關注server-socket channel的accept事件
從上面的常量定義可以看到,在注冊一個通道時可以關注該通道上的多個感興趣事件,感興趣事件之間使用“或”運算。
int intrestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE
注冊通道使用下面的方法:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
從上面代碼可以看到,需要将channel設定成非阻塞的,是以,FileChannel不可注冊到selector上。因為,FileChannel不可工作在非阻塞模式下。而所有套接字通道都可以。
3.SelectionKey
從上面的通道注冊方法可以看到,register方法會傳回一個SelectionKey對象。通過該對象可以得到以下幾個屬性:
interest集合
ready集合
channel
selector
附加對象
(1)interest集合
可以通過SelectionKey#interestOps()方法擷取 注冊的感興趣的事件。
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
(2)ready集合
ready集合是通道已經準備好的就緒事件集合。
也可以使用如下方法擷取一個boolean值:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
(3)channel和selector
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
(4)附加對象
将一個對象或者更多資訊附着在SelectionKey上。
**selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();**
也可以在使用register向selector注冊channel時附加對象。
4.通過Selector選擇通道
(1)select()方法
一旦在Selector上注冊了感興趣事件的通道channel,調用select方法就可以傳回這些感興趣事件就緒的通道。select方法有以下三種重載版本。
a.select() 阻塞到至少有一個通道在你注冊的事件上就緒
b.select(long timeOut) 阻塞到至少有一個通道在你注冊的事件上就緒或者逾時timeOut
c.selectNow() 立即傳回。如果沒有就緒的通道則傳回0
select方法的傳回值表示就緒通道的個數。
(2)selectedKeys()
調用select方法并且傳回了一個或多個就緒的通道之後,就可以調用selectedKeys方法來傳回已就緒通道的集合。
就像調用register方法時會傳回感興趣事件的通道的一個SelectionKey一樣。這個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 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();
}
上面代碼周遊這個已就緒(已選擇)通道的集合,并分别處理就緒事件。
注意每次循環調用都要記得remove。因為selector不會自己從已選擇集合中移除selectionKey執行個體。必須在處理完通道時自己移除。這樣,在下次select時,會将這個就緒通道添加到已選擇通道集合中。
SelectionKey#channel()需要将channel類型轉換成需要處理的channel類型,如ServerSocketChannel或者SocketChannel。
5.wakeUp()
當一個Selector調用select方法時,會阻塞直到有感興趣的事件通道就緒。但是也可在其他線程中在那個selector上調用wakeUp方法,使阻塞在select上的線程立即傳回。
如果調用wakeUp時并沒有select線程阻塞,則下次調用select時會立即傳回。
6.close()
用完selector之後可以通過調用close方法來關閉它,此時會将所有注冊在該selector上的selectionKey執行個體失效。但是相應的channel并沒有關閉。
示例代碼:
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == ) 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();
}
}