1. 程式人生 > >小師妹學JavaIO之:用Selector來發好人卡

小師妹學JavaIO之:用Selector來發好人卡

[toc] # 簡介 NIO有三寶:Buffer,Channel,Selector少不了。本文將會介紹NIO三件套中的最後一套Selector,並在理解Selector的基礎上,協助小師妹發一張好人卡。我們開始吧。 # Selector介紹 小師妹:F師兄,最近我的桃花有點旺,好幾個師兄莫名其妙的跟我打招呼,可是我一心向著工作,不想談論這些事情。畢竟先有事業才有家嘛。我又不好直接拒絕,有沒有什麼比較隱晦的方法來讓他們放棄這個想法? > 更多內容請訪問[www.flydean.com](www.flydean.com) 這個問題,我沉思了大約0.001秒,於是給出了答案:給他們發張好人卡吧,應該就不會再來糾纏你了。 小師妹:F師兄,如果給他們發完好人卡還沒有用呢? 那就只能切斷跟他們的聯絡了,來個一刀兩斷。哈哈。 這樣吧,小師妹你最近不是在學NIO嗎?剛好我們可以用Selector來模擬一下發好人卡的過程。 假如你的志偉師兄和子丹師兄想跟你建立聯絡,每個人都想跟你建立一個溝通通道,那麼你就需要建立兩個channel。 兩個channel其實還好,如果有多個人都想同時跟你建立聯絡通道,那麼要維持這些通道就需要保持連線,從而浪費了資源。 但是建立的這些連線並不是時時刻刻都有訊息在傳輸,所以其實大多數時間這些建立聯絡的通道其實是浪費的。 如果使用Selector就可以只啟用一個執行緒來監聽通道的訊息變動,這就是Selector。 ![](https://img-blog.csdnimg.cn/20200520142919874.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 從上面的圖可以看出,Selector監聽三個不同的channel,然後交給一個processor來處理,從而節約了資源。 # 建立Selector 先看下selector的定義: ~~~java public abstract class Selector implements Closeable ~~~ Selector是一個abstract類,並且實現了Closeable,表示Selector是可以被關閉的。 雖然Selector是一個abstract類,但是可以通過open來簡單的建立: ~~~java Selector selector = Selector.open(); ~~~ 如果細看open的實現可以發現一個很有趣的現象: ~~~java public static Selector open() throws IOException { return SelectorProvider.provider().openSelector(); } ~~~ open方法呼叫的是SelectorProvider中的openSelector方法。 再看下provider的實現: ~~~java public SelectorProvider run() { if (loadProviderFromProperty()) return provider; if (loadProviderAsService()) return provider; provider = sun.nio.ch.DefaultSelectorProvider.create(); return provider; } }); ~~~ 有三種情況可以載入一個SelectorProvider,如果系統屬性指定了java.nio.channels.spi.SelectorProvider,那麼從指定的屬性載入。 如果沒有直接指定屬性,則從ServiceLoader來載入。 最後如果都找不到的情況下,使用預設的DefaultSelectorProvider。 關於ServiceLoader的用法,我們後面會有專門的文章來講述。這裡先不做多的解釋。 # 註冊Selector到Channel中 ~~~java ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress("localhost", 9527)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); ~~~ 如果是在伺服器端,我們需要先建立一個ServerSocketChannel,繫結Server的地址和埠,然後將Blocking設定為false。因為我們使用了Selector,它實際上是一個非阻塞的IO。 > 注意FileChannels是不能使用Selector的,因為它是一個阻塞型IO。 小師妹:F師兄,為啥FileChannel是阻塞型的呀?做成非阻塞型的不是更快? 小師妹,我們使用FileChannel的目的是什麼?就是為了讀檔案呀,讀取檔案肯定是一直讀一直讀,沒有可能讀一會這個channel再讀另外一個channel吧,因為對於每個channel自己來講,在檔案沒讀取完之前,都是繁忙狀態,沒有必要在channel中切換。 最後我們將建立好的Selector註冊到channel中去。 # SelectionKey SelectionKey表示的是我們希望監聽到的事件。 總的來說,有4種Event: * SelectionKey.OP_READ 表示伺服器準備好,可以從channel中讀取資料。 * SelectionKey.OP_WRITE 表示伺服器準備好,可以向channel中寫入資料。 * SelectionKey.OP_CONNECT 表示客戶端嘗試去連線服務端 * SelectionKey.OP_ACCEPT 表示伺服器accept一個客戶端的請求 ~~~java public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << 2; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4; ~~~ 我們可以看到上面的4個Event是用位運算來定義的,如果將這個四個event使用或運算合併起來,就得到了SelectionKey中的interestOps。 和interestOps類似,SelectionKey還有一個readyOps。 一個表示感興趣的操作,一個表示ready的操作。 最後,SelectionKey在註冊的時候,還可以attach一個Object,比如我們可以在這個物件中儲存這個channel的id: ~~~java SelectionKey key = channel.register( selector, SelectionKey.OP_ACCEPT, object); key.attach(Object); Object object = key.attachment(); ~~~ object可以在register的時候傳入,也可以呼叫attach方法。 最後,我們可以通過key的attachment方法,獲得該物件。 # selector 和 SelectionKey 我們通過selector.select()這個一個blocking操作,來獲取一個ready的channel。 然後我們通過呼叫selector.selectedKeys()來獲取到SelectionKey物件。 在SelectionKey物件中,我們通過判斷ready的event來處理相應的訊息。 # 總的例子 接下來,我們把之前將的串聯起來,先建立一個小師妹的ChatServer: ~~~java public class ChatServer { private static String BYE_BYE="再見"; public static void main(String[] args) throws IOException, InterruptedException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress("localhost", 9527)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); ByteBuffer byteBuffer = ByteBuffer.allocate(512); while (true) { selector.select(); Set