四、Selector选择器

  • Post author:
  • Post category:其他


接着上一章的那个比喻,如果我们把Buffer缓冲区看做码头,Channel通道看做航道的话,那么Selector选择器可以看做海上的一个调度中心,一个调度中心可以监听多个航道的事件,当然,这需要航道归属在这个调度中心的编制下。



创建一个Selector

Selector selector = Selector.open();



将Channel注册在某个Selector的管辖范围之下

Selector是一个对象,它可以被注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。

channel.register(selector, SelectionKey.OP_ACCEPT);

上面这行代码是有返回值的,这个方法会返回一个SelectionKey类型的结构。这个SelectionKey是NIO中事务处理的一个关键中间点,通过SelectionKey我们可以获取很多信息,可以说SelectionKey是整个NIO的业务大脑,关于SelectionKey,我将会放在下一章进行说明,毕竟自己目前的理解还不够深。



Selector如何选择就绪的通道

//这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
selector.select();



NIO明明是非阻塞的IO,为何会阻塞

这是第一章Java NIO学习(一)NIO相关概念中说的内容,作为非阻塞的IO操作,NIO为何会有这么一个可能会造成阻塞的方法呢,别急,selector对象有很多方法可以解决阻塞的问题!

//阻塞在select()方法上的线程也可以立刻返回,不阻塞
selector.selectNow();

//可以设置超时时间,防止进程阻塞
selector.select(long timeout);

//可以唤醒阻塞状态下的selector
selector.wakeup();



选择器-多路复用器使用示例

具体详细使用示例请看:多路复用器-selector单线程版本

/**
 * 初始化服务器端信息
 */
private void initServerSocket(){
    try {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(serverPort));//绑定端口号
        System.out.println("step1 : new ServerSocket(" + serverPort+ ") ");
        serverSocketChannel.configureBlocking(false);//设置服务端非阻塞模式
        /**
         * //打开多路复用器
         */
        selector = Selector.open();
        /**
         * 把服务端注册到多路复用器当中,并监听一个网络行为---》OP_ACCEPT
         *
         * Interest Set
         * 监听的Channel通道触发了一个事件意思是该事件已经就绪。
         * 一个channel成功连接到另一个服务器称为”连接就绪“。
         * 一个server socket channel准备号接收新进入的连接称为”接收就绪“。
         * 一个有数据可读的通道可以说是”读就绪“。
         * 一个等待写数据的通道可以说是”写就绪“。
         * 这四种事件用SelectionKey的四个常量来表示:
         *     SelectionKey.OP_CONNECT
         *     SelectionKey.OP_ACCEPT
         *     SelectionKey.OP_READ
         *     SelectionKey.OP_WRITE
         */
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * 客户端连接建立处理类
 * @param selectionKey
 */
private void acceptHandler(SelectionKey selectionKey){
    try {
        /**
         * 由于在之前已经把ServerSocketChannel
         * <p>serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</p>
         * 已经注册到多路复用器(selector)中,因此现阶段可以直接把服务端通道直接取出
         */
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
        /**
         * 调用accept接口从服务端通道中获取客户端连接
         */
        SocketChannel clientSocketChannel = serverSocketChannel.accept();
        /**
         * 设置客户端连接也为非阻塞状态
         */
        clientSocketChannel.configureBlocking(false);

        /**
         * 创建一个8字节的数据缓冲区
         * 暂且不论该缓冲区大小是否足够
         * 该缓冲区的作用为:******一个客户端通道对应一个数据缓冲区,防止缓冲区公用
         */
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(8192);
        /**
         * 把新的客户端连接也注册到多路复用器当中,并且注册监听事件为读取就绪
         * 这样在新的<p>SelectionKey</p>  中就即可以拿到自己的客户端,也可以拿到与客户端绑定的缓冲区
         *
         * 附加选项---byteBuffer
         */
        SelectionKey clientSelectionKey = clientSocketChannel.register(selector,SelectionKey.OP_READ,byteBuffer);
        System.out.println("--------------------------------------------------");
        System.out.println("------新客户端进入------:" + clientSocketChannel.getRemoteAddress());
        System.out.println("--------------------------------------------------");
    } catch (IOException e) {
        e.printStackTrace();
    }
}