4 Java I/O:AIO和NIO中的Selector

您好,我是湘王,這是我的博客園 , 歡迎您來,歡迎您再來~
在Java NIO的三大核心中 , 除了Channel和Buffer , 剩下的就是Selector了 。有的地方叫它選擇器,也有叫多路復用器的(比如Netty) 。
之前提過,數據總是從Channel讀取到Buffer , 或者從Buffer寫入到Channel , 單個線程可以監聽多個Channel——Selector就是這個線程背后的實現機制(所以得名Selector) 。

4 Java I/O:AIO和NIO中的Selector

文章插圖
Selector通過控制單個線程處理多個Channel,如果應用打開了多個Channel,但每次傳輸的流量都很低,使用Selector就會很方便(至于為什么 , 具體到Netty中再分析) 。所以使用Selector的好處就顯而易見:用最少的資源實現最多的操作 , 避免了線程切換帶來的開銷 。
還是以代碼為例來演示Selector的作用 。新建一個類 , 在main()方法中輸入下面的代碼:
/** * NIO中的Selector * * @author xiangwang */public class TestSelector {public static void main(String args[]) throws IOException {// 創建ServerSocketChannelServerSocketChannel channel1 = ServerSocketChannel.open();channel1.socket().bind(new InetSocketAddress("127.0.0.1", 8080));channel1.configureBlocking(false);ServerSocketChannel channel2 = ServerSocketChannel.open();channel2.socket().bind(new InetSocketAddress("127.0.0.1", 9090));channel2.configureBlocking(false);// 創建一個Selector對象Selector selector = Selector.open();// 按照字面意思理解,應該是這樣的:selector.register(channel, event);// 但其實是這樣的:channel.register(selector, SelectionKey.OP_READ);// 四種監聽事件:// OP_CONNECT(連接就緒)// OP_ACCEPT(接收就緒)// OP_READ(讀就緒)// OP_WRITE(寫就緒)// 注冊Channel到Selector,事件一旦被觸發,監聽隨之結束SelectionKey key1 = channel1.register(selector, SelectionKey.OP_ACCEPT);SelectionKey key2 = channel2.register(selector, SelectionKey.OP_ACCEPT);// 模板代碼:在編寫程序時,大多數時間都是在模板代碼中添加相應的業務代碼while(true) {int readyNum = selector.select();if (readyNum == 0) {continue;}Set<SelectionKey> selectedKeys = selector.selectedKeys();// 輪詢for (SelectionKey key : selectedKeys) {Channel channel = key.channel();if (key.isConnectable()) {if (channel == channel1) {System.out.println("channel1連接就緒");} else {System.out.println("channel2連接就緒");}} else if (key.isAcceptable()) {if (channel == channel1) {System.out.println("channel1接收就緒");} else {System.out.println("channel2接收就緒");}}// 觸發后刪除,這里不刪// it.remove();}}}}代碼寫好后啟動ServerSocketChannel服務,可以看到我這里已經啟動成功:
4 Java I/O:AIO和NIO中的Selector

文章插圖
然后在網上下載一個叫做SocketTest.jar的工具(在一些工具網站下載的時候當心中毒,如果不放心,可以私信我,給你地址),雙擊打開,并按下圖方式執行:
4 Java I/O:AIO和NIO中的Selector

文章插圖
點擊「Connect」可以看到變化:
4 Java I/O:AIO和NIO中的Selector

文章插圖
然后點擊「Disconnect」,再輸入「9090」后,再點擊「Connect」試試:
4 Java I/O:AIO和NIO中的Selector

文章插圖
可以看到結果顯示結果變了:
4 Java I/O:AIO和NIO中的Selector

文章插圖
兩次連接,打印了三條信息:說明selector的輪詢在起作用(因為Set<SelectionKey>中包含了所有處于監聽的SelectionKey) 。但是「接收就緒」監聽事件僅執行了一次就再不響應 。如果感興趣的話你可以把OP_READ、OP_WRITE這些事件也執行一下試試看 。
因為Selector是單線程輪詢監聽多個Channel,那么如果Selector(線程)之間需要傳遞數據,怎么辦呢?——Pipe登場了 。Pipe就是一種用于Selector之間數據傳遞的「管道」 。
先來看個圖:
4 Java I/O:AIO和NIO中的Selector

文章插圖
可以清楚地看到它的工作方式 。
【4 Java I/O:AIO和NIO中的Selector】還是用代碼來解釋 。
/** * NIO中的Pipe * * @author xiangwang */public class TestPipe {public static void main(String args[]) throws IOException {// 打開管道Pipe pipe = Pipe.open();// 將Buffer數據寫入到管道Pipe.SinkChannel sinkChannel = pipe.sink();ByteBuffer buffer = ByteBuffer.allocate(32);buffer.put("ByteBuffer".getBytes());// 切換到寫模式buffer.flip();sinkChannel.write(buffer);// 從管道讀取數據Pipe.SourceChannel sourceChannel = pipe.source();buffer = ByteBuffer.allocate(32);sourceChannel.read(buffer);System.out.println(new String(buffer.array()));// 關閉管道sinkChannel.close();sourceChannel.close();}}

推薦閱讀