7 Java NIO Selector-翻译
Selector是一個Java NIO中能夠檢測一到多個Channel,并且決定哪個通道已經準備好讀或寫。通過這種方式可以使用一個線程管理多個通道,因此可以用來管理多個網絡連接。
為什么要使用Selector
使用一個線程來管理多個通道的優勢是你只需要更少的線程來管理通道。事實上,你可以只使用一個線程來管理所有的通道。對操作系統來說,線程間的切換的開銷是很大的,并且每一個線程都需要消耗操作系統的資源(內存)。因此,線程的使用是越少越好。
但是,需要記住,現在的操作系統和CPU在多任務處理方面變得越來越好。所以多線程的開銷隨著時間的推移變得越來越小。事實上,如果一個CPU是多核的,如果不進行多任務處理,反而在浪費CPU的能力。不管怎么說,關于那種設計的討論應該放在另一篇的文章里。在這里,只要知道,通過Selector,只需要一個線程來管理多個通道就可以了。
下圖是通過Selector使用一個線程來處理三個Channel的例子。
創建Selector
創建一個Selector只需要通過Selector.open()方法即可,像這樣:
Selector selector = Selector.open(); 復制代碼向通道注冊Selector
為了讓Channel使用Selector,必須讓Channel注冊Selector。這可以通過SelectableChannel.register()方法來實現,像這樣:
channel.configureBolcking(false); SelectionKey key = channel.register(selector,SelectionKey.OP_READ); 復制代碼當使用Selector時,channel必須是非阻塞模式。這意味著FileChannel不能使用Selector,因為FileChannel是阻塞的。Socket Channel都可以。
注意register方法的第二個參數,這是一個“interest set”,意思是在Selector監聽Channel時對哪些事件感興趣。共有 四種不同的事件類型可以監聽。
- Connect
- Accept
- Read
- Write
一個通道觸發一個事件意味著通道已經準備好這個事件了。因此,一個通道成功連接到另一臺服務器是一個“Connect ready”事件,一個服務器接受新進來的連接是一個“accept"事件。一個通道已經準備好數據是一個”ready“事件。一個能將準備寫數據是一個”write"事件。
這四種類型的事件代表四種不同的SelectionKey常量。
當對多個事件感興趣時,可以使用或運算符,像這個:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 復制代碼下面還會對interest set作進一步簡單介紹。
Interest Set
如“向通道注冊選擇器”一節中那樣,Interest Set是你所選擇的感興趣的集合。可以通過SelectionKey來讀寫Interest Set,像這樣:
int interestSet = selectionKey.interestOps();boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; 復制代碼正如你所看到的,你可以通過AND與指定的SelectionKey常量進行運算來判斷特定的事件是否在關注興趣集合中。
Ready Set
Ready Set是一系列的已經準備好的操作集合。在一次選擇(Selection)之后,你會首先訪問這個ready set。Selection將會在下一小節中進行介紹。可以通過如下方式訪問ready set。
int readySet = selectionKey.readOps(); 復制代碼可以用像檢測interest集合那樣的方法,來檢測channel中什么事件或操作已經就緒。但是,也可以使用以下四個方法,它們都會返回一個布爾類型:
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable(); 復制代碼Channel + Selector
從SelectionKey訪問Channel和Selector很簡單。如下:
Channel channel = selectionKey.channel();Selector selector = selectionKey.selector(); 復制代碼Attaching Objects
可以將一個對象或更多信息附加到SelectionKey上。例如,你可以附加一個與通道一起使用的Buffer或者包含聚集數據的某個對象。如下所示:
selectionKey.attach(theObject); Object attacheObj = selectionKey.attachment(); 復制代碼你也可以在Channel注冊Selector時附加對象。如下所示:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject); 復制代碼Selecting Channels via a Selector
一旦將一個或多個通道注冊到選擇器上,就可以調用select方法。這些方法返回你所感興趣的事件(如連接、接受、讀或寫)已經準備就緒的那些通道。
下面是select()方法。
int select(); int select(long timeout); int selectNow(); 復制代碼select()方法會阻塞直到所注冊的事件準備好。
select( lont timeout)方法跟select()方法一樣阻塞,但它最大阻塞時間為timeout。
selectNow()方法并不會阻塞,它立即返回無論是否有通道準備好。
方法返回的值表明有多少通道已經準備好。那就是說,從調用select()方法開始共有多少通道準備好。如果你調用select()方法并且返回1,說明有一個Channel已經準備好。如果你再次調用select方法,又一個通道準備好了,它又會返回1。如果對第一個已經準備好的通道沒有進行任何處理,第二次將返回2。但在每次調用之間,只有一個通道準備就緒。
selectedKeys()
一旦你調用select方法,它的返回值表明有一個或多個通道已經準備好了。已經準備好了的channel可以通過selected key set進行訪問,具體是通過調用 selectedKeys()方法。如下所示。
Set<SelectionKey> selectedKeys = selector.selectedKeys(); 復制代碼當向Selector注冊Channel時,Channel.register()方法會返回一個SelectionKey 對象。這個對象代表了注冊到該Selector的通道。可以通過SelectionKey的selectedKeySet()方法訪問這些對象。
可以遍歷這個已選擇的鍵集合來訪問就緒的通道。如下:
Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> 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(); } 復制代碼這個循環遍歷已選擇鍵集中的每個鍵,并檢測各個鍵所對應的通道的就緒事件。
注意每次迭代末尾的keyIterator.remove()方法調用。Selector并不會自己主動刪除selected key。當處理完成之后,必須進行手動處理。當下次channel準備好以后,它將會再次加入到Selected keys集合中。
SelectionKey.channel()方法返回的Channel必須強制轉換成對應的Channel。例如ServerSocketChannel或SocketChannel。
wakeUp()
某個線程調用select()方法后阻塞了,即使沒有通道已經就緒,也有辦法讓其從select()方法返回。只要讓其它線程在第一個線程調用select()方法的那個對象上調用Selector.wakeup()方法即可。阻塞在select()方法上的線程會立馬返回。
如果另外一個線程調用了wakeup方法,但此時并沒有任何線程阻塞了。下個調用select方法的線程將會立刻wakeup。
close()
用完Selector后調用其close()方法會關閉該Selector,且使注冊到該Selector上的所有SelectionKey實例無效。通道本身并不會關閉。
完整的Selector例子
Selector selector = Selector.open();channel.configureBlocking(false);SelectionKey key = channel.register(selector, SelectionKey.OP_READ);while(true) {int readyChannels = selector.select();if(readyChannels == 0) continue;Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> 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();} } 復制代碼總結
以上是生活随笔為你收集整理的7 Java NIO Selector-翻译的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 之泛型详解
- 下一篇: Centos 7.4 安装ipython