NIO(1、概述)
NIO(2、Buffer)
NIO(3、Channel)
NIO(4、Selector)html
前面兩個章節都描述了Buffer和Channel,那這個章節就描述NIO三個最核心部分的最後一塊內容 - 選擇器(Selector)
java
在前面的章節中描述過多路複用,一個線程經過選擇器處理和管理多個通道。因而可知,選擇器是用來處理多個通道並監聽其通道事件的組件。socket
Selector selector = Selector.open();
ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);
在註冊通道以前,把通道設置成非阻塞模式,觀察源碼會發現 register() 會校驗當前通道是否爲非阻塞模式,當是阻塞模式時,會拋出IllegalBlockingModeException 異常。在前面一個章節也提過,爲何FileChannel沒有繼承SelectableChannel,由於它不須要多路複用,因此在使用通道的時候,只有FileChannel不能向選擇器註冊通道,凡是繼承SelectableChannel都可以向選擇器註冊通道。
註冊通道方法的第二個參數是SelectionKey中定義的操做類型,你能夠填入任何你感興趣的操做類型,只要這個通道支持,一樣,在執行 register() 方法時也會校驗該通道是否可以支持該操做。
註冊方法一樣也會返回一個SelectionKey對象。線程
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);
selector.select();
參數5000是5秒,參數以毫秒爲單位。這個方法會一直阻塞5秒,5秒以內若是沒有通道事件就緒的話程序會往下運行:對象
selector.select(5000);
selectNow()其實就是非阻塞,不管有無通道事件就緒,程序都會向下執行:blog
selector.selectNow();
這三個方法的本質區別無非是選擇器阻塞或者等待一個或多個通道的事件就緒有多長時間。繼承
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();
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); }
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; }
SelectableChannel selectableChannel = selectionKey.channel(); Selector sel = selectionKey.selector();