netty

NiO chanel通道与选择器Selector

2021-04-15  本文已影响0人  GavinZZW

常 用 的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();
            }
        }
上一篇下一篇

猜你喜欢

热点阅读