天天看點

NIO三大元件之 selector

channel的注冊中心selector是整個NIO的核心,選擇器實作了I/O多路複用。使得單一線程有辦法同時對多個socket通道實作監控并及時發現需要處理的IO事件。選擇的使用比較簡單,主要是三個步驟:

  • 1、将通道注冊到一個通道上

    這裡建立裡一個Selector,并将

    ServerSocketChanne

    l注冊到selector上
    //建立一個selector對象
            Selector sel = Selector.open();
    
            // 建立severSocketChannel,并設定為非阻塞的模式
            ServerSocketChannel server = sel.provider().openServerSocketChannel();
            server.configureBlocking(false);
    
            //綁定通道到指定的端口
            ServerSocket socket = server.socket();
            socket.bind(new InetSocketAddress(8080));
    
            //向selector注冊感興趣的事件
            server.register(sel, SelectionKey.OP_ACCEPT);
               
  • 2、選擇器執行select()方法阻塞擷取已經等待就緒的事件

    下面是擷取key的示例:

    ByteBuffer buffer = ByteBuffer.allocate(1024);
            try {
                while(true){
                    //這是一個阻塞的動作
                    selector.select();
                    Set<SelectionKey> keys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = keys.iterator();
    
                    while(iterator.hasNext()){
                        SelectionKey key  = iterator.next();
                        //将目前的key 删除掉,防止重複處理
                        iterator.remove();
                        processKey(key,selector, buffer);
                    }
    
    
                }
            }catch (Exception e){
                e.printStackTrace();
            }
               
  • 3、使用擷取到的key來反向擷取channel,然後進行邏輯處理

    下面是處理key的示例:

    private void processKey(SelectionKey key, Selector selector, ByteBuffer buffer) throws IOException {
            if(key.isAcceptable()){
                ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
                SocketChannel accept = serverSocket.accept();
                accept.configureBlocking(false);
                accept.register(selector,SelectionKey.OP_READ,SelectionKey.OP_WRITE);
            }else  if(key.isReadable()){ //讀事件
                SocketChannel channel = (SocketChannel) key.channel();
                int len = channel.read(buffer);
    
                if(len > 0 ){
                    buffer.flip();
                    String content = new String(buffer.array());
                    SelectionKey register = channel.register(selector, SelectionKey.OP_WRITE);
                    register.attach(content);
    
                }else{
                    channel.close();
                }
            }else if(key.isWritable()){ //可寫事件
                SocketChannel channel = (SocketChannel) key.channel();
                String attach = (String) key.attachment();
                ByteBuffer wrap = ByteBuffer.wrap(("輸出内容:" + attach).getBytes());
                //
                channel.write(wrap);
    
            }
        }
               

這裡我們需要簡單的介紹下

SelectionKey

的四個事件:

  • OP_READ:讀事件,目前channel發生讀事件,将會獲得
  • OP_WRITE:寫事件。目前channel寫事件就緒的時候觸發
  • OP_CONNECT:channel連接配接成功事件。目前channel連接配接事件發生的時候觸發
  • OP_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;