NIO(4、Selector)

目錄

NIO(1、概述)
NIO(2、Buffer)
NIO(3、Channel)
NIO(4、Selector)html

Selector

前面兩個章節都描述了Buffer和Channel,那這個章節就描述NIO三個最核心部分的最後一塊內容 - 選擇器(Selector)
  java

如何使用

  在前面的章節中描述過多路複用,一個線程經過選擇器處理和管理多個通道。因而可知,選擇器是用來處理多個通道並監聽其通道事件的組件。socket

  • Create
      只須要調用 open() 便可建立一個Selector對象:
Selector selector = Selector.open();
  • Register
      經過 register() 方法註冊通道:
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);

  在註冊通道以前,把通道設置成非阻塞模式,觀察源碼會發現 register() 會校驗當前通道是否爲非阻塞模式,當是阻塞模式時,會拋出IllegalBlockingModeException 異常。在前面一個章節也提過,爲何FileChannel沒有繼承SelectableChannel,由於它不須要多路複用,因此在使用通道的時候,只有FileChannel不能向選擇器註冊通道,凡是繼承SelectableChannel都可以向選擇器註冊通道。
  註冊通道方法的第二個參數是SelectionKey中定義的操做類型,你能夠填入任何你感興趣的操做類型,只要這個通道支持,一樣,在執行 register() 方法時也會校驗該通道是否可以支持該操做。
  註冊方法一樣也會返回一個SelectionKey對象。線程

  • Attach Object
      註冊通道的 register() 方法有一個重載方法,能夠向選擇器註冊通道的時候,選擇想要帶上的附加對象:
public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;

  例如,使用時附加上一個字符串:rest

String ch_name = "123";
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT,ch_name);

  獲取這個字符串可經過 attachment() 來獲取:code

// 接收數據
String ch_name_accept = (String) selectionKey.attachment();

  固然,註冊時返回的SelectionKey對象也能夠在使用時候附加你想要的附加對象:htm

selectionKey.attach(ch_name);
  • Block
      由於是一個線程經過選擇器來操做通道,那麼選擇器在操做通道時,一定在處理一個通道的時候,另外一個事件已就緒的通道處於等待狀態。在肯定一個通道事件就緒以後,才能去操做這個通道。上文中講到使用註冊方法register使用的代碼示例,將ServerSocketChannel對象向選擇器註冊,同時關注了這個通道的OP_ACCEPT操做類型事件,那麼咱們何時能肯定該通道的accept事件就緒,能夠操做這個通道了。選擇器爲咱們提供了三個重載的 select() 方法,這三個方法的主要功能就是選擇是否阻塞直到該選擇器中的通道所關注的事件就緒,來讓程序繼續往下運行。
      首先看 select() 方法,該方法會一直阻塞下去,直到選擇器中的通道關注的事件就緒:
selector.select();

  參數5000是5秒,參數以毫秒爲單位。這個方法會一直阻塞5秒,5秒以內若是沒有通道事件就緒的話程序會往下運行:對象

selector.select(5000);

  selectNow()其實就是非阻塞,不管有無通道事件就緒,程序都會向下執行:blog

selector.selectNow();

  這三個方法的本質區別無非是選擇器阻塞或者等待一個或多個通道的事件就緒有多長時間。繼承

  • Keys & SelectionKeys
      咱們每爲一個通道執行 register() 註冊方法,就會返回一個SelectionKey,那麼這個選擇器全部已就緒的SelectionKey就是經過selectedKeys()來獲取:
Set<SelectionKey> selectionKeys = selector.selectedKeys();

  通常這個方法是在select() 以後執行,由於到這一步就意味着要經過這個輪詢每一個就緒的通道。

Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    if (key.isAcceptable()) {
        // 執行通道的操做
    }
    //執行完成移除
    iterator.remove();
}

  到這裏說的是已就緒的通道,那麼全部的 SelectionKey 集能夠經過 keys() 方法獲取:

Set<SelectionKey> keys = selector.keys();
  • wake up
      在使用Selector對象的 select() 或者 select(long) 方法時候,當前線程極可能一直阻塞下去,那麼用另外一個線程去執行 Selector.wakeUp() 方法會喚醒當前被阻塞的線程,使其 select() 當即返回。
      固然,若是當前線程沒有阻塞,那麼執行了wakeUp() 方法以後,下一個線程的 select() 方法會被當即返回,再也不被阻塞下去。
  • close
      顯然,close() 方法可以關閉當前的選擇器。
      當一個線程當前呈阻塞狀態,那麼停止這種狀態須要執行選擇器的 wakeUp() 方法,close()方法的實現正是這麼作的,先喚醒被阻塞的線程,而後繼續接下來的操做。接下來就會會置空全部的通道、全部就緒的SelectionKey,讓這個選擇器上的輪詢組件也閒置下來。

SelectionKey

  SelectionKey的功能相似於通道的一個註冊令牌。
  這個類定義了4個操做類型,每種操做類型都對應了相應的事件,經過監聽這幾種不一樣的事件,在觸發該事件時表示所對應的操做已準備就緒:

操做類型 描述
OP_READ 1 << 0 讀操做
OP_WRITE 1 << 2 寫操做
OP_CONNECT 1 << 3 鏈接socket操做
OP_ACCEPT 1 << 4 接受socket操做

  這裏得提一句,全部繼承SelectableChannel的通道都會定義本身可以支持的操做類型,能夠經過具體通道的 validOps() 方法查看,例如SocketChannel支持read、write、connect這幾種操做:

// SocketChannel類 
public final int validOps() {
        return (SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT);
}
  • interestOps & readyOps
      這是SelectionKey的實現類中定義的變量:
private volatile int interestOps;
private int readyOps;

  interestOps用來存儲感興趣的操做集,readyOps用來存儲已經就緒的操做集。
  其中 interestOps() 方法和 nioInterestOps() 都會返回interestOps,不一樣的是interestOps()會校驗是否已執行 cancel() ,若是已經取消則會拋出 CancelledKeyException 異常。readyOps一樣也有 readyOps() 和 nioReadyOps() 方法,邏輯與interestOps幾乎一致。
  觀察這段SelectionKey抽象類已經實現的代碼:

// SelectionKey 類
public final boolean isAcceptable() {
        return (readyOps() & OP_ACCEPT) != 0;
}

  當判斷是否訪問就緒的時候,只要 readyOps() 與相應的操做類型相與,非零就返回true,表明接受請求操做已就緒。這個是SelectionKey已提供的方法,可是SelectionKey並未提供一樣返回boolean判斷某個操做在interestOps集是否存在,咱們能夠本身實現這些方法:

private boolean isInterestRead(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_READ;
}
private boolean isInterestWrite(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
}
private boolean isInterestConnect(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
}
private boolean isInterestAccept(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
}
  • Channel、Selector
      在SelectionKey中獲取通道或者選擇器只須要調用其中的兩個方法便可:
SelectableChannel selectableChannel = selectionKey.channel();
Selector sel = selectionKey.selector();
相關文章
相關標籤/搜索