美文网首页netty
NiO chanel通道与选择器Selector

NiO chanel通道与选择器Selector

作者: GavinZZW | 来源:发表于2021-04-15 23:48 被阅读0次

    常 用 的Channel实现类类:
    FileChannel , DatagramChannel ,ServerSocketChannel和SocketChannel 。
    FileChannel 用于文件的数据读写,
    DatagramChannel 用于 UDP 的数据读写,
    ServerSocketChannel 和SocketChannel 用于 TCP 的数据读写。
    【ServerSocketChanne类似 ServerSocket , SocketChannel 类似 Socket】

    SocketChannel 与ServerSocketChannel
    类似 Socke和ServerSocket,可以完成客户端与服务端数据的通信工作.

    1.ServerSocketChannel(服务端)

    服务端:

    1. 打开一个服务端通道
    2. 绑定对应的端口号
    3. 通道默认是阻塞的,需要设置为非阻塞
    4. 检查是否有客户端连接 有客户端连接会返回对应的通道
    5. 获取客户端传递过来的数据,并把数据放在byteBuffer这个缓冲区中
    6. 给客户端回写数据
    7. 释放资源

    示例代码

     public static void main(String[] args) throws IOException, InterruptedException {
            //1. 打开一个服务端通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //2. 绑定对应的端口号
            serverSocketChannel.bind(new InetSocketAddress(9999));
            //3. 通道默认是阻塞的,需要设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            System.out.println("服务端启动成功....");
            while (true) {
                //4. 检查是否有客户端连接 有客户端连接会返回对应的通道
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel == null) {
                    System.out.println("没有客户端连接...我去做别的事情");
                    Thread.sleep(2000);
                    continue;
                }
                //5. 获取客户端传递过来的数据,并把数据放在byteBuffer这个缓冲区中
                ByteBuffer allocate = ByteBuffer.allocate(1024);
                //返回值
                //正数: 表示本地读到有效字节数
                //0: 表示本次没有读到数据
                //-1: 表示读到末尾
                int read = socketChannel.read(allocate);
                System.out.println("客户端消息:" + new String(allocate.array(), 0,
                        read, StandardCharsets.UTF_8));
                //6. 给客户端回写数据
                socketChannel.write(ByteBuffer.wrap("没钱".getBytes(StandardCharsets.UTF_8)));
                //7. 释放资源
                socketChannel.close();
            }
        }
    

    2.SocketChannel(客户端)

    1. 打开通道
    2. 设置连接IP和端口号
    3. 写出数据
    4. 读取服务器写回的数据
    5. 释放资源
     public static void main(String[] args) throws IOException {
            //1. 打开通道
            SocketChannel socketChannel = SocketChannel.open();
            //2. 设置连接IP和端口号
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
            //3. 写出数据
            socketChannel.write(ByteBuffer.wrap("老板.还钱吧!".getBytes(StandardCharsets.UTF_8)));
            //4. 读取服务器写回的数据
            ByteBuffer allocate = ByteBuffer.allocate(1024);
            int read = socketChannel.read(allocate);
            System.out.println("服务端消息:" +
                    new String(allocate.array(), 0, read, StandardCharsets.UTF_8));
            //5. 释放资源
            socketChannel.close();
        }
    
    

    3. Selector

    介绍
    可以用一个线程,处理多个的客户端连接,就会使用到NIO的Selector(选择器). Selector 能够检测
    多个注册的服务端通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的
    处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
    不使用selector情况下:


    image.png

    在这种没有选择器的情况下,对应每个连接对应一个处理线程. 但是连接并不能马上就会发送信息,所以还
    会产生资源浪费

    使用selector:


    image.png

    只有在通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都
    创建一个线程,不用去维护多个线程, 避免了多线程之间的上下文切换导致的开销

    Selector常用方法:

    Selector.open() : //得到一个选择器对象
    selector.select() : //阻塞 监控所有注册的通道,当有对应的事件操作时, 会将SelectionKey放入
    集合内部并返回事件数量
    selector.select(1000): //阻塞 1000 毫秒,监控所有注册的通道,当有对应的事件操作时, 会将
    SelectionKey放入集合内部并返回
    selector.selectedKeys() : // 返回存有SelectionKey的集合

    SelectionKey常用方法

    SelectionKey.isAcceptable(): 是否是连接继续事件
    SelectionKey.isConnectable(): 是否是连接就绪事件
    SelectionKey.isReadable(): 是否是读就绪事件
    SelectionKey.isWritable(): 是否是写就绪事件
    SelectionKey中定义的4种事件:
    SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器
    可以接收这个连接了
    SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户端与服务器的连接已经建立成功
    SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操
    作了(通道目前有数据,可以进行读操作了)
    SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用
    于写操作)

    Selector 编码

    服务端实现步骤:

    1. 打开一个服务端通道
    2. 绑定对应的端口号
    3. 通道默认是阻塞的,需要设置为非阻塞
    4. 创建选择器
    5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
    6. 检查选择器是否有事件
    7. 获取事件集合
    8. 判断事件是否是客户端连接事件SelectionKey.isAcceptable()
    9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
    10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
    11. 得到客户端通道,读取数据到缓冲区
    12. 给客户端回写数据
    13. 从集合中删除对应的事件, 因为防止二次处理

    代码示例

     public static void main(String[] args) throws IOException {
            //1. 打开一个服务端通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //2. 绑定对应的端口号
            serverSocketChannel.bind(new InetSocketAddress(9999));
            //3. 通道默认是阻塞的,需要设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //4. 创建选择器
            Selector selector = Selector.open();
            //5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务端启动成功.....");
            while (true) {
                //6. 检查选择器是否有事件
                int select = selector.select(2000);
                if (select == 0) {
                    System.out.println("没有事件发生....");
                    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. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        System.out.println("有客户端连接.....");
                        //将通道必须设置成非阻塞的状态.因为selector选择器需要轮询监听每个通道的事件
                        socketChannel.configureBlocking(false);
                        //指定监听事件为OP_READ 读就绪事件
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    //10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
                    if (key.isReadable()) {
                        //11.得到客户端通道,读取数据到缓冲区
                        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. 给客户端回写数据
                            socketChannel.write(ByteBuffer.wrap("没钱".getBytes(StandardCharsets.UTF_8)));
                            socketChannel.close();
                        }
                    }
                    //13. 从集合中删除对应的事件, 因为防止二次处理.
                    iterator.remove();
                }
            }
    

    相关文章

      网友评论

        本文标题:NiO chanel通道与选择器Selector

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