標籤:java nio 翻譯 selector
selector是Java NIO的組件可以檢查一個或多個NIO的channel,並且決定哪個channel是為讀寫準備好了。這種方式,單個線程可以管理多個channel,也就是多個網路連接。
為什麼使用選取器
優點就是更少的線程去處理多個通道。實際上,你可以使用一個線程去處理所有的通道。作業系統中線程的切換是很費資源的,而且每個線程本身也佔用了一些資源(記憶體)。所以使用的線程越少越好!
現在的作業系統和CPU在多任務上變得越來越好,所以多線程的開銷也變得更小了。事實上,如果一個CPU有多個核心,不用多線程可能是一種浪費。不管怎麼說,設計討論應該是在另一篇文章說。在這裡,知道用selector,單線程去處理多通道就足夠了。
建立選取器
通過調用Selector.open()方法建立。
註冊通道
channel.configureBlocking(false);配置通道為非阻塞模式
channel.register(selector,SelectionKey.OP_ACCEPT);通過該方法註冊
使用selector,channel必須是非阻塞模式。意味著FileChannel不用使用selector,因為FileChannel不能轉為非阻塞模式。SocketChannel可以正常使用。
注意register方法的第二個參數。這是一個興趣集合,意思是通過selector監聽這個channel時,對什麼樣的事件感興趣。有如下幾種:
1、Connect
2、Accept
3、Read
4、Write
一個通道觸發了一個事件意思就是對該事件準備就緒了。所以,一個channel和伺服器串連成功了就是串連就緒。ServerSocketChannel接受了串連就是接受就緒。一個通道有資料準備好被讀了就是讀就緒。一個通道準備寫入資料就是寫就緒。
這四中事件通過SelectionKey的四個常量來定義:
1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READ
4、SelectionKey.OP_WRITE
如果你對多個事件有興趣,可以如下來寫:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
在文章靠後將會回到興趣集來講解。
SelectionKey’s
正如前面所述,當你通過selector給通道註冊,register方法將返回一個SelectionKey對象,這個對象包含了一些你感興趣的屬性:
·The interest set
·The ready set
·The Channel
·The Selector
·An attached object (optional)
Interest Set
interest集合是你感興趣的事件集合。你可以通過SelectionKey讀寫興趣集合,如下:
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;
你可以通過&操作找出某個事件是否是興趣集合的。
Ready Set
ready集合是channel為哪些操作已經就緒了。在一次選擇後,你會首先訪問ready集合,如下:
int readySet = selectionKey.readyOps();
同樣你可以像上面提到的方法一樣通過&來測試哪些操作已經就緒了。但是同樣你可以通過如下方法來得到:
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 attachedObj = selectionKey.attachment();
你也可以再register的時候就附加對象
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Selecting Channels via a Selector
一旦你通過selector註冊了多個通道,你可以調用selecto方法。這些方法返回為興趣集合(串連,接收,讀,寫)事件就緒的通道。換言之,如果你對對讀就緒的通道感興趣, 你就會通過select方法返回讀就緒的通道。
以下是select方法:
·int select()
·int select(long timeout)
·int selectNow()
select()方法會阻塞直到至少一個通道是為你註冊的事件就緒的。
select(long timeout)和select()一樣,除了它阻塞會有一個逾時時間。
selectNow()沒有阻塞,無論什麼通道,就緒就立刻返回。
select()方法返回的int表示有多少通道就緒了,即自從最後一次調用select()方法以來,有多少通道就緒了。如果你調用select方法返回1,說明有一個通道就緒了,你再次調用返回1,說明另一個通道就緒了。如果你對第一個就緒的通道什麼都不做,你現在就有兩個就緒通道,但是僅僅只有一個通道就緒在每次select方法調用過程中。
selectedKeys()
一旦你調用了一個select()方法並且傳回值,表明一個或多個通道就緒了,你可以通過selected key集合訪問就緒通道,通過調用selectedKeys方法實現,如下:
Set selectedKeys = selector.selectedKeys();
當你註冊了一個通道事件時會返回一個SelectionKey對象。這個對象表示註冊到該Selector上的通道。你可以通過selectedKeySet訪問這些keys。如下:
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();}
這個迴圈遍曆selected key集合。並檢測各個鍵對應的通道就緒事件。
注意每次迭代末尾的keyIterator.remove()調用。Selector不會自己從已選擇鍵集中移除SelectionKey執行個體。必須在處理完通道時自己移除。下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中。
SelectionKey.channel()方法返回的channel需要轉型成你需要的,例如ServerSocketChannel,SocketChannel等。
wakeUp()
一個線程調用select方法阻塞了,即使沒有就緒通道,也可以讓select方法返回。讓其它線程通過剛剛調用select方法的Selector對象調用wakeup方法即可。阻塞在select方法上的線程會立即返回。
如果其它線程調用了wakeup,但是當前沒有線程阻塞在select,那麼下一個調用select方法的線程會立即喚醒。
close()
用完Selector要調用close方法。關閉Selector並且將所有註冊在selector上的鍵集作廢。通道自己不會關閉。
完整執行個體
package nio;import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.util.Iterator;import java.util.Set;public class SelectorDemo { public static void main(String[] args) throws IOException { //開啟伺服器通訊端通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //非阻塞模式 ssc.configureBlocking(false); //擷取與此通道關聯的伺服器通訊端 ServerSocket ss = ssc.socket(); //服務綁定 ss.bind(new InetSocketAddress(8990)); //開啟一個選取器 Selector selector = Selector.open(); //在該選取器上註冊通道事件 SelectionKey registerKey = ssc.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyChannels = selector.select(); if(readyChannels==0) { System.out.println("No Channel Is Ready !"); continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { System.out.println("接收操作!"); }else if(key.isConnectable()) { System.out.println("串連操作!"); }else if(key.isReadable()) { System.out.println("讀操作!"); }else if(key.isWritable()) { System.out.println("寫操作!"); } keyIterator.remove(); } } }}
以上代碼注意,在註冊事件時只能是ACCEPT,其它事件在外面註冊都會導致程式運行失敗,因為其它所有事件都是在ACCEPT後才能夠註冊的,所以要注意這一點。
下一節:等待
【JAVA】【NIO】7、Java NIO Selector