天天看點

NIO邊看邊記 之 selector選擇器(六)

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();
  }
}