NiO chanel通道與選擇器Selector

常 用 的Channel實現(xiàn)類類:
FileChannel , DatagramChannel ,ServerSocketChannel和SocketChannel 。
FileChannel 用于文件的數(shù)據(jù)讀寫,
DatagramChannel 用于 UDP 的數(shù)據(jù)讀寫,
ServerSocketChannel 和SocketChannel 用于 TCP 的數(shù)據(jù)讀寫。
【ServerSocketChanne類似 ServerSocket , SocketChannel 類似 Socket】

SocketChannel 與ServerSocketChannel
類似 Socke和ServerSocket,可以完成客戶端與服務(wù)端數(shù)據(jù)的通信工作.

1.ServerSocketChannel(服務(wù)端)

服務(wù)端:

  1. 打開一個服務(wù)端通道
  2. 綁定對應(yīng)的端口號
  3. 通道默認是阻塞的,需要設(shè)置為非阻塞
  4. 檢查是否有客戶端連接 有客戶端連接會返回對應(yīng)的通道
  5. 獲取客戶端傳遞過來的數(shù)據(jù),并把數(shù)據(jù)放在byteBuffer這個緩沖區(qū)中
  6. 給客戶端回寫數(shù)據(jù)
  7. 釋放資源

示例代碼

 public static void main(String[] args) throws IOException, InterruptedException {
        //1. 打開一個服務(wù)端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 綁定對應(yīng)的端口號
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //3. 通道默認是阻塞的,需要設(shè)置為非阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("服務(wù)端啟動成功....");
        while (true) {
            //4. 檢查是否有客戶端連接 有客戶端連接會返回對應(yīng)的通道
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel == null) {
                System.out.println("沒有客戶端連接...我去做別的事情");
                Thread.sleep(2000);
                continue;
            }
            //5. 獲取客戶端傳遞過來的數(shù)據(jù),并把數(shù)據(jù)放在byteBuffer這個緩沖區(qū)中
            ByteBuffer allocate = ByteBuffer.allocate(1024);
            //返回值
            //正數(shù): 表示本地讀到有效字節(jié)數(shù)
            //0: 表示本次沒有讀到數(shù)據(jù)
            //-1: 表示讀到末尾
            int read = socketChannel.read(allocate);
            System.out.println("客戶端消息:" + new String(allocate.array(), 0,
                    read, StandardCharsets.UTF_8));
            //6. 給客戶端回寫數(shù)據(jù)
            socketChannel.write(ByteBuffer.wrap("沒錢".getBytes(StandardCharsets.UTF_8)));
            //7. 釋放資源
            socketChannel.close();
        }
    }

2.SocketChannel(客戶端)

  1. 打開通道
  2. 設(shè)置連接IP和端口號
  3. 寫出數(shù)據(jù)
  4. 讀取服務(wù)器寫回的數(shù)據(jù)
  5. 釋放資源
 public static void main(String[] args) throws IOException {
        //1. 打開通道
        SocketChannel socketChannel = SocketChannel.open();
        //2. 設(shè)置連接IP和端口號
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
        //3. 寫出數(shù)據(jù)
        socketChannel.write(ByteBuffer.wrap("老板.還錢吧!".getBytes(StandardCharsets.UTF_8)));
        //4. 讀取服務(wù)器寫回的數(shù)據(jù)
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        int read = socketChannel.read(allocate);
        System.out.println("服務(wù)端消息:" +
                new String(allocate.array(), 0, read, StandardCharsets.UTF_8));
        //5. 釋放資源
        socketChannel.close();
    }

3. Selector

介紹
可以用一個線程,處理多個的客戶端連接,就會使用到NIO的Selector(選擇器). Selector 能夠檢測
多個注冊的服務(wù)端通道上是否有事件發(fā)生,如果有事件發(fā)生,便獲取事件然后針對每個事件進行相應(yīng)的
處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求。
不使用selector情況下:


image.png

在這種沒有選擇器的情況下,對應(yīng)每個連接對應(yīng)一個處理線程. 但是連接并不能馬上就會發(fā)送信息,所以還
會產(chǎn)生資源浪費

使用selector:


image.png

只有在通道真正有讀寫事件發(fā)生時,才會進行讀寫,就大大地減少了系統(tǒng)開銷,并且不必為每個連接都
創(chuàng)建一個線程,不用去維護多個線程, 避免了多線程之間的上下文切換導(dǎo)致的開銷

Selector常用方法:

Selector.open() : //得到一個選擇器對象
selector.select() : //阻塞 監(jiān)控所有注冊的通道,當有對應(yīng)的事件操作時, 會將SelectionKey放入
集合內(nèi)部并返回事件數(shù)量
selector.select(1000): //阻塞 1000 毫秒,監(jiān)控所有注冊的通道,當有對應(yīng)的事件操作時, 會將
SelectionKey放入集合內(nèi)部并返回
selector.selectedKeys() : // 返回存有SelectionKey的集合

SelectionKey常用方法

SelectionKey.isAcceptable(): 是否是連接繼續(xù)事件
SelectionKey.isConnectable(): 是否是連接就緒事件
SelectionKey.isReadable(): 是否是讀就緒事件
SelectionKey.isWritable(): 是否是寫就緒事件
SelectionKey中定義的4種事件:
SelectionKey.OP_ACCEPT —— 接收連接繼續(xù)事件,表示服務(wù)器監(jiān)聽到了客戶連接,服務(wù)器
可以接收這個連接了
SelectionKey.OP_CONNECT —— 連接就緒事件,表示客戶端與服務(wù)器的連接已經(jīng)建立成功
SelectionKey.OP_READ —— 讀就緒事件,表示通道中已經(jīng)有了可讀的數(shù)據(jù),可以執(zhí)行讀操
作了(通道目前有數(shù)據(jù),可以進行讀操作了)
SelectionKey.OP_WRITE —— 寫就緒事件,表示已經(jīng)可以向通道寫數(shù)據(jù)了(通道目前可以用
于寫操作)

Selector 編碼

服務(wù)端實現(xiàn)步驟:

  1. 打開一個服務(wù)端通道
  2. 綁定對應(yīng)的端口號
  3. 通道默認是阻塞的,需要設(shè)置為非阻塞
  4. 創(chuàng)建選擇器
  5. 將服務(wù)端通道注冊到選擇器上,并指定注冊監(jiān)聽的事件為OP_ACCEPT
  6. 檢查選擇器是否有事件
  7. 獲取事件集合
  8. 判斷事件是否是客戶端連接事件SelectionKey.isAcceptable()
  9. 得到客戶端通道,并將通道注冊到選擇器上, 并指定監(jiān)聽事件為OP_READ
  10. 判斷是否是客戶端讀就緒事件SelectionKey.isReadable()
  11. 得到客戶端通道,讀取數(shù)據(jù)到緩沖區(qū)
  12. 給客戶端回寫數(shù)據(jù)
  13. 從集合中刪除對應(yīng)的事件, 因為防止二次處理

代碼示例

 public static void main(String[] args) throws IOException {
        //1. 打開一個服務(wù)端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 綁定對應(yīng)的端口號
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //3. 通道默認是阻塞的,需要設(shè)置為非阻塞
        serverSocketChannel.configureBlocking(false);
        //4. 創(chuàng)建選擇器
        Selector selector = Selector.open();
        //5. 將服務(wù)端通道注冊到選擇器上,并指定注冊監(jiān)聽的事件為OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服務(wù)端啟動成功.....");
        while (true) {
            //6. 檢查選擇器是否有事件
            int select = selector.select(2000);
            if (select == 0) {
                System.out.println("沒有事件發(fā)生....");
                continue;
            }
            //7. 獲取事件集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                //8. 判斷事件是否是客戶端連接事件SelectionKey.isAcceptable()
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    //9. 得到客戶端通道,并將通道注冊到選擇器上, 并指定監(jiān)聽事件為OP_READ
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("有客戶端連接.....");
                    //將通道必須設(shè)置成非阻塞的狀態(tài).因為selector選擇器需要輪詢監(jiān)聽每個通道的事件
                    socketChannel.configureBlocking(false);
                    //指定監(jiān)聽事件為OP_READ 讀就緒事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                //10. 判斷是否是客戶端讀就緒事件SelectionKey.isReadable()
                if (key.isReadable()) {
                    //11.得到客戶端通道,讀取數(shù)據(jù)到緩沖區(qū)
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer allocate = ByteBuffer.allocate(1024);
                    int read = socketChannel.read(allocate);
                    if (read > 0) {
                        System.out.println("客戶端消息:" + new String(allocate.array(), 0, read
                                , StandardCharsets.UTF_8));
                        //12. 給客戶端回寫數(shù)據(jù)
                        socketChannel.write(ByteBuffer.wrap("沒錢".getBytes(StandardCharsets.UTF_8)));
                        socketChannel.close();
                    }
                }
                //13. 從集合中刪除對應(yīng)的事件, 因為防止二次處理.
                iterator.remove();
            }
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容