美文网首页
辛星2018年nio教程第八篇:初识Selector

辛星2018年nio教程第八篇:初识Selector

作者: 辛星0913 | 来源:发表于2018-04-08 02:23 被阅读0次

    先打个小广告,关注辛星教程,我的微信号xinxing0913,该项目源码所在的github地址: https://github.com/xinxing0913/xinxing-nio-guide

    在传统的服务器模型中,一般都是用一个线程去处理一个连接,等这个连接处理完毕,这个线程就归还到线程池中,从而保证有条不紊的执行。

    但是当连接数量非常多的时候,我们的线程数量就会非常多,这在连接数量非常多的情况下会有不小的挑战,为了解决这个问题,nio引入了selector这个概念。

    selector即选择器,它表示检查一个或者多个nio的channel的状态是否处于可读可写的状态,它可以用于实现单线程管理多个channel,从而管理多个网络连接。

    我们的一般使用方式就变成了多个连接对应一个selector,然后这个selector属于一个线程,这样就完成了一个线程内处理多个连接。

    我们可以使用如下的方式来创建一个selector:

    Selector selector = Selector.open();
    

    然后我们把非阻塞的channel注册到selector上,注意这里的channel必须是非阻塞的:

    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
    

    对于register的第二个参数,它表示关注集合,也就是我们关注的channel的状态,它的中可以有四个:
    SelectionKey.OP_CONNECT表示连接状态
    SelectionKey.OP_ACCEPT表示接受状态
    SelectionKey.OP_READ表示读取数据
    SelectionKey.OP_WRITE表示写入数据

    当一个channel与服务端连接成功后,它就处于连接就绪状态,如果是可读的时候,就处于可读就绪状态。多个状态之间可以使用"|"来进行组合。

    我们可以通过selectionKey的channel()方法获取Channel,我们可以通过它的selector()方法来获取Selector。

    我们可以通过Selector对象的三个方法来获取一个或者多个Channel,它有三个方法:
    第一个是select()方法,它在返回channel前处于阻塞状态,它返回当前就绪的channel的个数。
    第二个是select(long timeout)方法,它在返回channel前也处于阻塞状态,它返回当前可以选择的channel的个数。
    第三个是selectNow()方法,它不会阻塞,它会立即返回有多少合适的channel。

    我们可以用selector来改造我们之前的基于TCP的服务端,我们这里新建范例代码如下:

    /**
     * 基于nio并且使用了selector的服务端
     */
    public class Demo11 {
        public static void main(String[] args) throws Exception {
            ServerSocketChannel channel = ServerSocketChannel.open();
            channel.configureBlocking(false);
            ServerSocket socket = channel.socket();
            socket.bind(new InetSocketAddress(8080));
            Selector selector = Selector.open();
            channel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器已启动...");
            while (true) {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();
    
                while(iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    handle(key, selector);
                }
            }
        }
    
        public static void handle(SelectionKey key, Selector selector) throws Exception{
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.finishConnect();
                socketChannel.register(selector, SelectionKey.OP_READ);
                System.out.println("客户端已连接...");
            } else if (key.isReadable()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                SocketChannel socketChannel = (SocketChannel) key.channel();
                socketChannel.read(byteBuffer);
                socketChannel.write(ByteBuffer.wrap("received!!".getBytes()));
                socketChannel.close();
                System.out.println("从客户端收到的信息:" + new String(byteBuffer.array()));
            } else {
                System.out.println("内部错误...");
            }
        }
    
    }
    

    然后我们这里的客户端可以用Selector去重新实现,也可以直接复用Demo9的代码,不过我这里在读和写之间加了一秒钟的等待时间来确保写操作完成,如下:

    /**
     * 基于nio实现的TCP客户端
     */
    public class Demo12 {
        public static void main(String[] args) throws Exception{
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false); // 设置为非阻塞
            socketChannel.connect(new InetSocketAddress(8080));
            socketChannel.finishConnect(); // 等待连接完成
    
            // 向服务端发送一段文本
            byte[] bytes = "hello 辛星".getBytes();
            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
            socketChannel.write(byteBuffer);
    
            // 等待一秒钟
            Thread.sleep(1000);
    
            // 最多只接受服务端的1024个字节
            ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
            socketChannel.read(responseBuffer);
            System.out.println("服务端响应的数据:" + new String(responseBuffer.array()));
        }
    }
    

    我们首先运行服务器,如下所示:


    image.png

    然后我们运行Demo12这个客户端,如下所示:


    image.png

    然后我们在Demo11的控制台下就会看到对应的输出:


    image.png

    对于Selector,就暂时介绍到这里啦。

    相关文章

      网友评论

          本文标题:辛星2018年nio教程第八篇:初识Selector

          本文链接:https://www.haomeiwen.com/subject/cvtbzxtx.html