日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

小师妹学JavaIO之:用Selector来发好人卡

發布時間:2024/2/28 java 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 小师妹学JavaIO之:用Selector来发好人卡 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 簡介
  • Selector介紹
  • 創建Selector
  • 注冊Selector到Channel中
  • SelectionKey
  • selector 和 SelectionKey
  • 總的例子
  • 總結

簡介

NIO有三寶:Buffer,Channel,Selector少不了。本文將會介紹NIO三件套中的最后一套Selector,并在理解Selector的基礎上,協助小師妹發一張好人卡。我們開始吧。

Selector介紹

小師妹:F師兄,最近我的桃花有點旺,好幾個師兄莫名其妙的跟我打招呼,可是我一心向著工作,不想談論這些事情。畢竟先有事業才有家嘛。我又不好直接拒絕,有沒有什么比較隱晦的方法來讓他們放棄這個想法?

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多內容請訪問www.flydean.com

這個問題,我沉思了大約0.001秒,于是給出了答案:給他們發張好人卡吧,應該就不會再來糾纏你了。

小師妹:F師兄,如果給他們發完好人卡還沒有用呢?

那就只能切斷跟他們的聯系了,來個一刀兩斷。哈哈。

這樣吧,小師妹你最近不是在學NIO嗎?剛好我們可以用Selector來模擬一下發好人卡的過程。

假如你的志偉師兄和子丹師兄想跟你建立聯系,每個人都想跟你建立一個溝通通道,那么你就需要創建兩個channel。

兩個channel其實還好,如果有多個人都想同時跟你建立聯系通道,那么要維持這些通道就需要保持連接,從而浪費了資源。

但是建立的這些連接并不是時時刻刻都有消息在傳輸,所以其實大多數時間這些建立聯系的通道其實是浪費的。

如果使用Selector就可以只啟用一個線程來監聽通道的消息變動,這就是Selector。

從上面的圖可以看出,Selector監聽三個不同的channel,然后交給一個processor來處理,從而節約了資源。

創建Selector

先看下selector的定義:

public abstract class Selector implements Closeable

Selector是一個abstract類,并且實現了Closeable,表示Selector是可以被關閉的。

雖然Selector是一個abstract類,但是可以通過open來簡單的創建:

Selector selector = Selector.open();

如果細看open的實現可以發現一個很有趣的現象:

public static Selector open() throws IOException {return SelectorProvider.provider().openSelector();}

open方法調用的是SelectorProvider中的openSelector方法。

再看下provider的實現:

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中

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一個客戶端的請求
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:

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:

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("你是個好人".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);} }

上面例子有兩點需要注意,我們在循環遍歷中,當selectionKey.isAcceptable時,表示服務器收到了一個新的客戶端連接,這個時候我們需要調用register方法,再注冊一個OP_READ事件到這個新的SocketChannel中,然后繼續遍歷。

第二,我們定義了一個stop word,當收到這個stop word的時候,會直接關閉這個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的讀取。

最后輸出結果:

server收到: INFO com.flydean.ChatServer - hello 小師妹! client收到: INFO com.flydean.ChatClient - response is 你是個好人 server收到: INFO com.flydean.ChatServer - 能不能? client收到: INFO com.flydean.ChatClient - response is 再見

解釋一下整個流程:志偉跟小師妹建立了一個連接,志偉向小師妹打了一個招呼,小師妹給志偉發了一張好人卡。志偉不死心,想繼續糾纏,小師妹回復再見,然后自己關閉了通道。

總結

本文介紹了Selector和channel在發好人卡的過程中的作用。

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/java-io-nio-selector/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等著您!

總結

以上是生活随笔為你收集整理的小师妹学JavaIO之:用Selector来发好人卡的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。