1. 概述
??Selector允許單線程處理多個 Channel。如果你的應用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。例如,在一個聊天服務器中。
??要使用Selector,得向Selector注冊Channel,然后調用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等。
2.Selector
??Selector是NIO中實現I/O多路復用的關鍵類。Selector實現了通過一個線程管理多個Channel,從而管理多個網絡連接的目的。
??僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道。事實上,可以只用一個線程處理所有的通道。對于操作系統來說,線程之間上下文切換的開銷很大,而且每個線程都要占用系統的一些資源(如內存)。因此,使用的線程越少越好。
??Selector
能夠在單個線程中處理多個通道,這樣可以減少多個線程造成上下文切換問題。
??Channel
代表這一個網絡連接通道,我們可以將Channel
注冊到Selector
中以實現Selector
對其的管理。一個Channel
可以注冊到多個不同的Selector中。
??當Channel
注冊到Selector
后會返回一個SelectionKey
對象,該SelectionKey
對象則代表這這個Channel
和它注冊的Selector
間的關系。并且SelectionKey
中維護著兩個很重要的屬性:interestOps、readyOps
??interestOps
是我們希望Selector
監聽Channel
的哪些事件。我們將我們感興趣的事件設置到該字段,這樣在selection
操作時,當發現該Channel
有我們所感興趣的事件發生時,就會將我們感興趣的事件再設置到readyOps
中,這樣我們就能得知是哪些事件發生了以做相應處理。
??想想一個場景:在一個養雞場,有這么一個人,每天的工作就是不停檢查幾個特殊的雞籠,如果有雞進來,有雞出去,有雞生蛋,有雞生病等等,就把相應的情況記錄下來,如果雞場的負責人想知道情況,只需要詢問那個人即可。
??在這里,這個人就相當Selector
,每個雞籠相當于一個SocketChannel
,每個線程通過一個Selector
可以管理多個SocketChannel
。
??為了實現Selector
管理多個SocketChannel
,必須將具體的SocketChannel
對象注冊到Selector
,并聲明需要監聽的事件(這樣Selector
才知道需要記錄什么數據),一共有4種事件:
1、connect:客戶端連接服務端事件,對應值為SelectionKey.OP_CONNECT(8) 2、accept:服務端接收客戶端連接事件,對應值為SelectionKey.OP_ACCEPT(16) 3、read:讀事件,對應值為SelectionKey.OP_READ(1) 4、write:寫事件,對應值為SelectionKey.OP_WRITE(4)
??每次請求到達服務器,都是從connect開始,connect成功后,服務端開始準備accept,準備就緒,開始讀數據,并處理,最后寫回數據返回。
過程:
1、創建ServerSocketChannel實例,并綁定制定端口; 2、創建Selector實例; 3、將serverSocketChannel注冊到Selector,并指定事件OP_ACCEPT,最底層的socket通過channel和selector建立關聯; 4、如果沒有準備號的socket,select方法會被阻塞一段時間并返回0; 5、如果底層有socket已經準備好,selector的select方法會返回socket的個數,而且selectedKeys方法會返回socket對應的事件(connect、accept、read or write); 6、根據時間類型,進行不同的處理邏輯。
在步驟3中,selector只注冊了serverSocketChannel的OP_ACCEPT事件
1、如果有客戶端A連接服務,執行select方法時,可以通過serverSocketChannel獲取客戶端A的socketChannel,并在selector上注冊socketChannel的OP_READ事件。
2、如果客戶端A發送數據,會觸發read事件,這樣下次輪詢調用select方法時,就能通過socketChannel讀取數據,同時在selector上注冊該socketChannel的OP_WRITE事件,實現服務器往客戶端寫數據
3.Selector(選擇器)的使用方法詳細描述
??NIO中實現非阻塞IO的核心設計Selector,Selector就是注冊各種IO事件的地方,而且當那些事件發生時,就是這個對象告訴我們所發生的事件。
??當有讀或者寫等任何注冊的事件發生時,可以從Selector
中獲得相應的SelectionKey
,同時從SelectionKey
中可以找到發生的事件和該事件所發生的具體的SelectableChannel
,以獲得客戶端發送過來的數據。
使用NIO中非阻塞IO編寫服務器處理程序,有三個步驟
1.向Selector對象注冊感興趣的事件 2.從Selector中獲取感興趣的事件 3.根據不同事件進行相應的處理
1) Selector的創建
??通過調用Selector.open()
方法創建一個Selector
對象,如下:
??Selector selector = Selector.open();
2)注冊Channel到Selector
channel.configureBlocking(false); SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Channel必須是非阻塞的。
??所以FileChannel
不適用Selector
,因為FileChannel
不能切換為非阻塞模式,更準確的來說是因為FileChannel
沒有繼承SelectableChannel
。Socket channel
可以正常使用。
SelectableChannel
抽象類 有一個 configureBlocking()
方法用于使通道處于阻塞模式或非阻塞模式。
abstract SelectableChannel configureBlocking(boolean block)
3)SelectionKey介紹
??一個SelectionKey
鍵表示了一個特定的通道對象和一個特定的選擇器對象之間的注冊關系。
key.attachment(); //返回SelectionKey的attachment,attachment可以在注冊channel的時候指定。
key.channel(); // 返回該SelectionKey對應的channel。
key.selector(); // 返回該SelectionKey對應的Selector。
key.interestOps(); //返回代表需要Selector監控的IO操作的bit mask
key.readyOps(); // 返回一個bit mask,代表在相應channel上可以進行的IO操作。
4)從Selector中選擇channel(Selecting Channels via a Selector)
??選擇器維護注冊過的通道的集合,并且這種注冊關系都被封裝在SelectionKey
當中.
??Selector
維護的三種類型SelectionKey
集合:
已注冊的鍵的集合(Registered key set)
已選擇的鍵的集合(Selected key set)
所有與選擇器關聯的通道所生成的鍵的集合稱為已經注冊的鍵的集合。并不是所有注冊過的鍵都仍然有效。這個集合通過 keys() 方法返回,并且可能是空的。這個已注冊的鍵的集合不是可以直接修改的;試圖這么做的話將引發java.lang.UnsupportedOperationException。
已取消的鍵的集合(Cancelled key set)
5)select()方法介紹:
??在剛初始化的Selector對象中,這三個集合都是空的。 通過Selector的select()
方法可以選擇已經準備就緒的通道 (這些通道包含你感興趣的的事件)。比如你對讀就緒的通道感興趣,那么select()
方法就會返回讀事件已經就緒的那些通道。下面是Selector
幾個重載的select()
方法:
??select()
??獲取就緒的 Channel,阻塞方法,沒有就緒的 Channel 就一直阻塞該線程。
??select(long timeout)
??獲取就緒的 Channel, 阻塞方法,阻塞 timeout 時間,如果超時還沒有就緒的 Channel,返回0,不做任何操作。
??selectNow()
??獲取就緒的 Channel,如果沒有就緒的就直接返回,不阻塞當前線程。
上面三個 select方法底層都是調用 lockAndDoSelect 方法。 lockAndDoSelect方法的參數值 說明: -1 : 一直阻塞,直到有就緒的 Channel 可處理 0 : 不阻塞 0: 表示阻塞多長時間
??select()
方法返回的int值表示有多少通道已經就緒,是自上次調用select()
方法后有多少通道變成就緒狀態。
??之前在select()
調用時進入就緒的通道不會在本次調用中被記入,而在前一次select()
調用進入就緒但現在已經不在處于就緒的通道也不會被記入。
??例如:首次調用select()
方法,如果有一個通道變成就緒狀態,返回了1,若再次調用select()
方法,如果另一個通道就緒了,它會再次返回1。如果對第一個就緒的channel
沒有做任何操作,現在就有兩個就緒的通道,但在每次select()
方法調用之間,只有一個通道就緒了。
??一旦調用select()
方法,并且返回值不為0時,則 可以通過調用Selector
的selectedKeys()
方法來訪問已選擇鍵集合 。如下:
Set selectedKeys=selector.selectedKeys();
進而可以放到和某SelectionKey
關聯的Selector
和Channel
。
歡迎關注公眾號