使用NIO的一個最大優勢就是客戶端於服務器自己的不再是阻塞式的,也就意味着服務器無需通過爲每個客戶端的鏈接而開啓一個線程。而是通過一個叫Selector的輪循器來不斷的檢測那個Channel有消息處理。
簡單來講,Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連接接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的Set集合,進行後續的I/O操作。
由於select操作只管對selectedKeys的集合進行添加而不負責移除,所以當某個消息被處理後我們需要從該集合裏去掉。
NIO中的選擇器(Selector)的作用就是維護註冊到選擇器中的通道集合,每一個通道與選擇器的關係封裝在選擇鍵(SelectionKey)中,實際上可以認爲選擇器維護的是選擇鍵集合。創建Selector對象使用Selector.open()。
Selector類主要維護三個集合:
- Registered key set(已註冊鍵集合):調用Selector的keys()方法可以獲取
- Selected key set(已選擇鍵集合):調用Selector的selectedKeys()方法獲取
- Cancelled key set(已取消鍵集合):已取消鍵集合是Selector對象的私有成員,外部無法訪問
只有繼承了SelectableChannel的類才能註冊到選擇器中,並且只有非阻塞模式的通道才能註冊到選擇器,如下代碼:
//創建一個套接字服務器,並註冊到選擇器
//創建選擇器
Selector selector = Selector.open();
//創建Socket服務器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//綁定65535端口
ssc.socket().bind(new InetSocketAddress(65535));
//設置通道爲非阻塞模式
ssc.configureBlocking(false);
//將通道註冊到選擇器,指定通道興趣是等待接收連接
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
實際使用中,一般使用while循環輪詢獲取註冊到選擇器中通道感興趣的操作,如下代碼:
while (true) {
int n = selector.select();
if (n > 0) {
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey keyy = iter.next();
iter.remove();
// ......
}
}
}
select()方法的作用是選擇註冊到選擇器中通道感興趣的鍵,此方法是阻塞的,直到有感興趣的事件發生;還可以使用select(10000)方法設置選擇鍵的超時時間,單位是Millisecond;還可以使用selectNow(),此方法是非阻塞,若沒有通道就緒會立即返回0。
不需要使用Selector時,調用close()方法可以關閉選擇器,關閉選擇器後,所有註冊到其中的選擇鍵會被設置爲無效狀態。
關閉選擇器後,試圖調用它的方法會拋出ClosedSelectorException,這是一個非運行時異常,所有在使用時有必要檢查選擇器是否打開,使用isOpen()方法。
註冊一個通道時可以在這個選擇鍵上設置一個Object對象,比如在接受連接操作中設置,在讀操作中獲取
SelectionKey keyy = iter.next();
iter.remove();
if(keyy.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) keyy.channel();
SocketChannel sc = channel.accept();
sc.register(selector, SelectionKey.OP_READ, "hello");
}
if(keyy.isReadable()){
//attach的值爲"hello"
Object attach = keyy.attachment();
SocketChannel sc = (SocketChannel) keyy.channel();
}
SelectionKey(選擇鍵對象)提供了下面方法:
- channel():獲取關聯的通道(SelectableChannel)
- selector():獲取關聯的選擇器(Selector)
- isValid():驗證維護選擇器與通道關係的SelectionKey是否有效
- cancel():取消鍵,調用一個已取消的鍵的方法將拋出CancelledKeyException
- interestOps():獲取這個key的興趣(可選擇操作)集合
- interestOps(int ops):設置這個key的興趣
- readyOps():返回這個key準備好的操作集合(興趣集合)
- isReadable():檢查選擇鍵的興趣是否爲可讀,實際上是通道的興趣,選擇鍵維護通道與選擇器關係
- isWritable():檢查選擇鍵的興趣是否爲可寫
- isConnectable():檢查選擇鍵的興趣是否爲連接
- isAcceptable():檢查選擇鍵的興趣是否爲接受
- attach(Object ob):設置一個Object數據到此key上
- attachment():獲取設置的Object數據
Selector(選擇器)提供了下面方法:
- open():打開一個選擇器
- isOpen():檢查一個選擇器實例是否打開
- provider():返回一個SelectorProvider
- keys():返回註冊鍵集合
- selectedKeys():返回已選擇鍵集合
- selectNow():立刻執行選擇,非阻塞,若沒有已準備好的通道則立即返回0
- select(long timeout):執行選擇,超過指定毫秒數則返回
- select():執行選擇,會一直阻塞直到有準備就緒的通道
- wakeup():停止選擇
- close():關閉選擇器