A Selector是一個(gè)Java NIO組件,可以檢查一個(gè)或多個(gè)NIO通道,并確定哪些通道已準(zhǔn)備就緒,例如讀取或?qū)懭搿_@樣一個(gè)線程可以管理多個(gè)通道,從而管理多個(gè)網(wǎng)絡(luò)連接。

為什么選擇器?

  使用單個(gè)線程來(lái)處理多個(gè)通道的優(yōu)點(diǎn)是您需要較少的線程來(lái)處理通道。你可以使用一個(gè)線程來(lái)處理你所有的頻道。線程之間的切換消耗系統(tǒng)資源較大,每個(gè)線程也占用操作系統(tǒng)中的一些資源(內(nèi)存)。所以你使用的線程越少越好。

  現(xiàn)代操作系統(tǒng)和CPU在多任務(wù)處理中變得越來(lái)越好,所以隨著時(shí)間的推移,多線程的開(kāi)銷越來(lái)越小。事實(shí)上,如果CPU具有多個(gè)內(nèi)核,處理少量任務(wù)時(shí)會(huì)浪費(fèi)CPU電源。

  這是一個(gè)線程使用Selector來(lái)管理3個(gè)通道的流程圖:

Java NIO:A Thread uses a Selector to handle 3 Channel's

創(chuàng)建選擇器

  Selector通過(guò)調(diào)用Selector.open()方法 創(chuàng)建一個(gè),如下所示:

Selector selector= Selector.open();

為選擇器注冊(cè)通道

  為了使用一個(gè)ChannelSelector你必須注冊(cè)ChannelSelector。這是使用SelectableChannel的register()方法完成的 ,如下所示:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector,SelectionKey.OP_READ);

  Channel必須在非阻塞模式與Selector使用。這意味著你不能將FileChannelSelector一起使用,因?yàn)?nbsp;FileChannel不能切換到非阻塞模式。Socket channels可以正常工作。

  注意register()方法的第二個(gè)參數(shù)。這是一個(gè)“興趣集”,意思是你可以通過(guò)Selector在通道監(jiān)聽(tīng)你需要的事件,。你可以監(jiān)聽(tīng)四種不同的事件:

  • Connect

  • Accept

  • Read

  • Write

  “第一事件”的頻道也被稱為“ready”的事件。因此,已成功連接到其他服務(wù)器的通道是“connect ready”。接受傳入連接的服務(wù)器套接字通道是“Accept”ready。具有準(zhǔn)備讀取的數(shù)據(jù)的通道“read”ready。準(zhǔn)備好向您寫(xiě)入數(shù)據(jù)的通道是“write”ready。

這四個(gè)事件由四個(gè)SelectionKey常量表示:

  1. SelectionKey.OP_CONNECT

  2. SelectionKey.OP_ACCEPT

  3. SelectionKey.OP_READ

  4. SelectionKey.OP_WRITE

如果您對(duì)多個(gè)事件感興趣,可以將常數(shù)在一起使用,如下所示:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey‘s

  當(dāng)您Channel使用Selector 該register()方法注冊(cè)時(shí)返回一個(gè)SelectionKey對(duì)象。此SelectionKey對(duì)象包含一些有趣的屬性:

  • The interest set

  • The ready set

  • The Channel

  • The Selector

  • An attached object (optional)

The interest set   ------  興趣集

  興趣集是您感興趣的“選擇”事件集??梢酝ㄟ^(guò)以下方式讀取和寫(xiě)入該興趣集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;

  可以看到將設(shè)置給定SelectionKey常數(shù)的選擇興趣集,以確定某個(gè)事件是否在興趣集中。

The ready set      -----就緒集

  準(zhǔn)備集是通道準(zhǔn)備好的一組操作。您將主要在選擇后訪問(wèn)就緒集。選擇在后面的部分進(jìn)行說(shuō)明。

  訪問(wèn)就緒集代碼展示:

int readySet = selectionKey.readyOps();

  您可以以與興趣集相同的方式測(cè)試,頻道準(zhǔn)備就緒的事件/操作。但是,您也可以使用這四種方法,這些方法都返回布爾值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

 Channel + Selector      ----頻道+選擇器

  訪問(wèn)通道+選擇器SelectionKey是不重要的。

   具體操作如下:

Channel channel = selectionKey.channel();
Selector selector= selectionKey.selector();

 attached object (optional)---附加對(duì)象

  您可以附加一個(gè)對(duì)象,SelectionKey這是一種方便識(shí)別給定頻道或?qū)⒏嘈畔⒏郊拥筋l道的方法。例如,您可以附加Buffer您正在使用的頻道,或者包含更多聚合數(shù)據(jù)的對(duì)象。以下是附加對(duì)象的方法:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

  您也可以附上已經(jīng)一個(gè)對(duì)象,而注冊(cè)Channel用 Selector,在register()方法。這是怎么樣的樣子:

SelectionKey key = channel.register(selector,SelectionKey.OP_READ,theObject);

通過(guò)選擇器選擇頻道

  一旦您注冊(cè)了一個(gè)或多個(gè)渠道,Selector您可以調(diào)用其中一種select()方法。這些方法返回對(duì)您感興趣的事件(連接,接受,讀取或?qū)懭耄皽?zhǔn)備好”的通道。換句話說(shuō),如果您對(duì)準(zhǔn)備閱讀的頻道感興趣,您將收到準(zhǔn)備從select()方法中閱讀的頻道。

以下是select()方法:

  • int select()

  • int select(長(zhǎng)時(shí)間超時(shí))

  • int selectNow()

  select() 阻止至少一個(gè)通道為您注冊(cè)的事件準(zhǔn)備好。

  select(long timeout)select()除了它阻塞最多timeout毫秒(參數(shù))外,還是一樣的。

  selectNow()根本不阻止 它立即返回任何通道準(zhǔn)備就緒。

  在int由返回的select()方法告訴很多渠道如何準(zhǔn)備。也就是說(shuō),自從你上次打電話以來(lái),已經(jīng)有多少個(gè)頻道準(zhǔn)備好了select()。如果您打電話select(),并且返回1,因?yàn)橐粋€(gè)通道已經(jīng)準(zhǔn)備就緒,并且再次撥打select()一個(gè)通道,并且還有一個(gè)通道已經(jīng)準(zhǔn)備就緒,它將再次返回1。如果您準(zhǔn)備好的第一個(gè)頻道沒(méi)有任何功能,您現(xiàn)在可以有2個(gè)準(zhǔn)備好的頻道,但每個(gè)select()通話之間只有一個(gè)頻道已經(jīng)準(zhǔn)備就緒。

selectedKeys()

  一旦您調(diào)用了一種select()方法,其返回值表示一個(gè)或多個(gè)通道已準(zhǔn)備好,您可以通過(guò)調(diào)用選擇器selectedKeys()方法通過(guò)“選定的鍵集”訪問(wèn)就緒通道。這是怎么樣的樣子:

設(shè)置<SelectionKey> selectedKeys = selector.selectedKeys();

  當(dāng)你注冊(cè)一個(gè)通道SelectorChannel.register()方法返回一個(gè)SelectionKey對(duì)象。該鍵表示通道與該選擇器的注冊(cè)。這些鍵可以通過(guò)該selectedKeySet()方法訪問(wèn)。從 SelectionKey

您可以迭代此選擇的密鑰集以訪問(wèn)就緒通道。這是怎么樣的樣子:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

Set<SelectionKey> selectedKeys = selector.selectedKeys();

keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()){
    
    SelectionKey key = keyIterator.next();    if(key.isAcceptable()){        //連接被ServerSocketChannel接受。
    } else if(key.isConnectable()){        //與遠(yuǎn)程服務(wù)器建立連接。
    } else if(key.isReadable()){        //一個(gè)頻道準(zhǔn)備好閱讀
    } else if(key.isWritable()){        //一個(gè)頻道準(zhǔn)備好寫(xiě)作了    }

    keyIterator.remove();
}

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

  該循環(huán)遍歷所選密鑰集中的密鑰。對(duì)于每個(gè)密鑰,它測(cè)試密鑰以確定密鑰引用的信道準(zhǔn)備好了。

  請(qǐng)注意keyIterator.remove()每次迭代結(jié)束時(shí)的呼叫。在 Selector不刪除SelectionKey從為自己選擇的關(guān)鍵實(shí)例。當(dāng)您完成處理頻道后,您必須這樣做。下一次通道變?yōu)椤熬途w”時(shí),Selector將再次將其添加到所選的鍵集。

  該SelectionKey.channel()方法返回的通道應(yīng)該被轉(zhuǎn)換為您需要使用的通道,例如ServerSocketChannel或SocketChannel等。

wakeup()方法

  調(diào)用select()被阻止的方法的線程select(),即使沒(méi)有通道尚未準(zhǔn)備就可以離開(kāi)該方法。這是由具有不同的線程調(diào)用完成Selector.wakeup() 的方法,Selector其中第一線呼吁 select()上。線程等待內(nèi)線select()然后立即返回。

  如果一個(gè)不同的線程調(diào)用wakeup(),并且當(dāng)前沒(méi)有線程被阻塞select(),那么調(diào)用的下一個(gè)線程select()將立即“喚醒”。

close()方法

  當(dāng)你完成Selector你調(diào)用它的close() 方法。這關(guān)閉并使所Selector注冊(cè)的所有SelectionKey 實(shí)例無(wú)效Selector。渠道本身沒(méi)有關(guān)閉。

全選擇器示例

  這是一個(gè)打開(kāi)一個(gè)完整的例子Selector,注冊(cè)一個(gè)通道(通道實(shí)例化被忽略),并且繼續(xù)監(jiān)視Selector 四個(gè)事件(接受,連接,讀取和寫(xiě)入)的“準(zhǔn)備”。

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector,SelectionKey.OP_READ);while(true){  int readyChannels = selector.select();  if(readyChannels == 0)繼續(xù);


  Set<SelectionKey> selectedKeys = selector.selectedKeys();

  keyIterator = selectedKeys.iterator();  while(keyIterator.hasNext()){

    SelectionKey key = keyIterator.next();    if(key.isAcceptable()){        //連接被ServerSocketChannel接受。
    } else if(key.isConnectable()){        //與遠(yuǎn)程服務(wù)器建立連接。
    } else if(key.isReadable()){        //一個(gè)頻道準(zhǔn)備好閱讀
    } else if(key.isWritable()){        //一個(gè)頻道準(zhǔn)備好寫(xiě)作了    }

    keyIterator.remove();
  }
}

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

http://www.cnblogs.com/kuoAT/p/7002068.html