小师妹学JavaIO之:用Selector来发好人卡
文章目錄
- 簡(jiǎn)介
- Selector介紹
- 創(chuàng)建Selector
- 注冊(cè)Selector到Channel中
- SelectionKey
- selector 和 SelectionKey
- 總的例子
- 總結(jié)
簡(jiǎn)介
NIO有三寶:Buffer,Channel,Selector少不了。本文將會(huì)介紹NIO三件套中的最后一套Selector,并在理解Selector的基礎(chǔ)上,協(xié)助小師妹發(fā)一張好人卡。我們開始吧。
Selector介紹
小師妹:F師兄,最近我的桃花有點(diǎn)旺,好幾個(gè)師兄莫名其妙的跟我打招呼,可是我一心向著工作,不想談?wù)撨@些事情。畢竟先有事業(yè)才有家嘛。我又不好直接拒絕,有沒有什么比較隱晦的方法來讓他們放棄這個(gè)想法?
更多精彩內(nèi)容且看:
- 區(qū)塊鏈從入門到放棄系列教程-涵蓋密碼學(xué),超級(jí)賬本,以太坊,Libra,比特幣等持續(xù)更新
- Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續(xù)更新
- Spring 5.X系列教程:滿足你對(duì)Spring5的一切想象-持續(xù)更新
- java程序員從小工到專家成神之路(2020版)-持續(xù)更新中,附詳細(xì)文章教程
更多內(nèi)容請(qǐng)?jiān)L問www.flydean.com
這個(gè)問題,我沉思了大約0.001秒,于是給出了答案:給他們發(fā)張好人卡吧,應(yīng)該就不會(huì)再來糾纏你了。
小師妹:F師兄,如果給他們發(fā)完好人卡還沒有用呢?
那就只能切斷跟他們的聯(lián)系了,來個(gè)一刀兩斷。哈哈。
這樣吧,小師妹你最近不是在學(xué)NIO嗎?剛好我們可以用Selector來模擬一下發(fā)好人卡的過程。
假如你的志偉師兄和子丹師兄想跟你建立聯(lián)系,每個(gè)人都想跟你建立一個(gè)溝通通道,那么你就需要?jiǎng)?chuàng)建兩個(gè)channel。
兩個(gè)channel其實(shí)還好,如果有多個(gè)人都想同時(shí)跟你建立聯(lián)系通道,那么要維持這些通道就需要保持連接,從而浪費(fèi)了資源。
但是建立的這些連接并不是時(shí)時(shí)刻刻都有消息在傳輸,所以其實(shí)大多數(shù)時(shí)間這些建立聯(lián)系的通道其實(shí)是浪費(fèi)的。
如果使用Selector就可以只啟用一個(gè)線程來監(jiān)聽通道的消息變動(dòng),這就是Selector。
從上面的圖可以看出,Selector監(jiān)聽三個(gè)不同的channel,然后交給一個(gè)processor來處理,從而節(jié)約了資源。
創(chuàng)建Selector
先看下selector的定義:
public abstract class Selector implements CloseableSelector是一個(gè)abstract類,并且實(shí)現(xiàn)了Closeable,表示Selector是可以被關(guān)閉的。
雖然Selector是一個(gè)abstract類,但是可以通過open來簡(jiǎn)單的創(chuàng)建:
Selector selector = Selector.open();如果細(xì)看open的實(shí)現(xiàn)可以發(fā)現(xiàn)一個(gè)很有趣的現(xiàn)象:
public static Selector open() throws IOException {return SelectorProvider.provider().openSelector();}open方法調(diào)用的是SelectorProvider中的openSelector方法。
再看下provider的實(shí)現(xiàn):
public SelectorProvider run() {if (loadProviderFromProperty())return provider;if (loadProviderAsService())return provider;provider = sun.nio.ch.DefaultSelectorProvider.create();return provider;}});有三種情況可以加載一個(gè)SelectorProvider,如果系統(tǒng)屬性指定了java.nio.channels.spi.SelectorProvider,那么從指定的屬性加載。
如果沒有直接指定屬性,則從ServiceLoader來加載。
最后如果都找不到的情況下,使用默認(rèn)的DefaultSelectorProvider。
關(guān)于ServiceLoader的用法,我們后面會(huì)有專門的文章來講述。這里先不做多的解釋。
注冊(cè)Selector到Channel中
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress("localhost", 9527));serverSocketChannel.configureBlocking(false);serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);如果是在服務(wù)器端,我們需要先創(chuàng)建一個(gè)ServerSocketChannel,綁定Server的地址和端口,然后將Blocking設(shè)置為false。因?yàn)槲覀兪褂昧薙elector,它實(shí)際上是一個(gè)非阻塞的IO。
注意FileChannels是不能使用Selector的,因?yàn)樗且粋€(gè)阻塞型IO。
小師妹:F師兄,為啥FileChannel是阻塞型的呀?做成非阻塞型的不是更快?
小師妹,我們使用FileChannel的目的是什么?就是為了讀文件呀,讀取文件肯定是一直讀一直讀,沒有可能讀一會(huì)這個(gè)channel再讀另外一個(gè)channel吧,因?yàn)閷?duì)于每個(gè)channel自己來講,在文件沒讀取完之前,都是繁忙狀態(tài),沒有必要在channel中切換。
最后我們將創(chuàng)建好的Selector注冊(cè)到channel中去。
SelectionKey
SelectionKey表示的是我們希望監(jiān)聽到的事件。
總的來說,有4種Event:
- SelectionKey.OP_READ 表示服務(wù)器準(zhǔn)備好,可以從channel中讀取數(shù)據(jù)。
- SelectionKey.OP_WRITE 表示服務(wù)器準(zhǔn)備好,可以向channel中寫入數(shù)據(jù)。
- SelectionKey.OP_CONNECT 表示客戶端嘗試去連接服務(wù)端
- SelectionKey.OP_ACCEPT 表示服務(wù)器accept一個(gè)客戶端的請(qǐng)求
我們可以看到上面的4個(gè)Event是用位運(yùn)算來定義的,如果將這個(gè)四個(gè)event使用或運(yùn)算合并起來,就得到了SelectionKey中的interestOps。
和interestOps類似,SelectionKey還有一個(gè)readyOps。
一個(gè)表示感興趣的操作,一個(gè)表示ready的操作。
最后,SelectionKey在注冊(cè)的時(shí)候,還可以attach一個(gè)Object,比如我們可以在這個(gè)對(duì)象中保存這個(gè)channel的id:
SelectionKey key = channel.register(selector, SelectionKey.OP_ACCEPT, object); key.attach(Object); Object object = key.attachment();object可以在register的時(shí)候傳入,也可以調(diào)用attach方法。
最后,我們可以通過key的attachment方法,獲得該對(duì)象。
selector 和 SelectionKey
我們通過selector.select()這個(gè)一個(gè)blocking操作,來獲取一個(gè)ready的channel。
然后我們通過調(diào)用selector.selectedKeys()來獲取到SelectionKey對(duì)象。
在SelectionKey對(duì)象中,我們通過判斷ready的event來處理相應(yīng)的消息。
總的例子
接下來,我們把之前將的串聯(lián)起來,先建立一個(gè)小師妹的ChatServer:
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<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iter = selectedKeys.iterator();while (iter.hasNext()) {SelectionKey selectionKey = iter.next();if (selectionKey.isAcceptable()) {register(selector, serverSocketChannel);}if (selectionKey.isReadable()) {serverResonse(byteBuffer, selectionKey);}iter.remove();}Thread.sleep(1000);}}private static void serverResonse(ByteBuffer byteBuffer, SelectionKey selectionKey)throws IOException {SocketChannel socketChannel = (SocketChannel) selectionKey.channel();socketChannel.read(byteBuffer);byteBuffer.flip();byte[] bytes= new byte[byteBuffer.limit()];byteBuffer.get(bytes);log.info(new String(bytes).trim());if(new String(bytes).trim().equals(BYE_BYE)){log.info("說再見不如不見!");socketChannel.write(ByteBuffer.wrap("再見".getBytes()));socketChannel.close();}else {socketChannel.write(ByteBuffer.wrap("你是個(gè)好人".getBytes()));}byteBuffer.clear();}private static void register(Selector selector, ServerSocketChannel serverSocketChannel)throws IOException {SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);} }上面例子有兩點(diǎn)需要注意,我們?cè)谘h(huán)遍歷中,當(dāng)selectionKey.isAcceptable時(shí),表示服務(wù)器收到了一個(gè)新的客戶端連接,這個(gè)時(shí)候我們需要調(diào)用register方法,再注冊(cè)一個(gè)OP_READ事件到這個(gè)新的SocketChannel中,然后繼續(xù)遍歷。
第二,我們定義了一個(gè)stop word,當(dāng)收到這個(gè)stop word的時(shí)候,會(huì)直接關(guān)閉這個(gè)client channel。
再看看客戶端的代碼:
public class ChatClient {private static SocketChannel socketChannel;private static ByteBuffer byteBuffer;public static void main(String[] args) throws IOException {ChatClient chatClient = new ChatClient();String response = chatClient.sendMessage("hello 小師妹!");log.info("response is {}", response);response = chatClient.sendMessage("能不能?");log.info("response is {}", response);chatClient.stop();}public void stop() throws IOException {socketChannel.close();byteBuffer = null;}public ChatClient() throws IOException {socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9527));byteBuffer = ByteBuffer.allocate(512);}public String sendMessage(String msg) throws IOException {byteBuffer = ByteBuffer.wrap(msg.getBytes());String response = null;socketChannel.write(byteBuffer);byteBuffer.clear();socketChannel.read(byteBuffer);byteBuffer.flip();byte[] bytes= new byte[byteBuffer.limit()];byteBuffer.get(bytes);response =new String(bytes).trim();byteBuffer.clear();return response;} }客戶端代碼沒什么特別的,需要注意的是Buffer的讀取。
最后輸出結(jié)果:
server收到: INFO com.flydean.ChatServer - hello 小師妹! client收到: INFO com.flydean.ChatClient - response is 你是個(gè)好人 server收到: INFO com.flydean.ChatServer - 能不能? client收到: INFO com.flydean.ChatClient - response is 再見解釋一下整個(gè)流程:志偉跟小師妹建立了一個(gè)連接,志偉向小師妹打了一個(gè)招呼,小師妹給志偉發(fā)了一張好人卡。志偉不死心,想繼續(xù)糾纏,小師妹回復(fù)再見,然后自己關(guān)閉了通道。
總結(jié)
本文介紹了Selector和channel在發(fā)好人卡的過程中的作用。
本文作者:flydean程序那些事
本文鏈接:http://www.flydean.com/java-io-nio-selector/
本文來源:flydean的博客
歡迎關(guān)注我的公眾號(hào):程序那些事,更多精彩等著您!
總結(jié)
以上是生活随笔為你收集整理的小师妹学JavaIO之:用Selector来发好人卡的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小师妹学JavaIO之:NIO中那些奇怪
- 下一篇: 小师妹学JVM之:JVM的架构和执行过程