NIO编程

NIO编程(同步非阻塞)

1、NIO介绍

NIO三大核心

  • 缓冲区Buffer

    • Buffer子类

    image-20211213170141268

    • Buffer常用API

      缓冲区对象创建
      static ByteBuffer allocate(长度) 创建byte类型的指定长度的缓冲区
      static ByteBuffer wrap(byte[] array) 创建一个有内容的byte类型缓冲区
      
      缓冲区对象添加数据
      int position()/position(intnewPosition)获得当前要操作的索引/修改当前要操作的索引位置
      int limit()/limit(int newLimit)最多能操作到哪个索引/修改最多能操作的索引位置
      int capacity() 返回缓冲区的总长度
      int remaining() 还有多少能操作索引个数  
      boolean hasRemaining() 是否还有能操作
      put(byte b)/put(byte[] src) 添加一个字节/添加字节数组
      
      
      缓冲区对象读取数据
      flip() 写切换读模式 limit设置position位置, position设置0
      get() 读一个字节
      get(byte[] dst) 读多个字节
      get(int index) 读指定索引的字节
      rewind() 将position设置为0,可以重复读
      clear() 切换写模式 position设置为0 , limit 设置为 capacity
      array() 将缓冲区转换成字节数组返回
      
  • 通道Change

    • 常 用 的Channel实现类类

      FileChannel 用于文件的数据读写
      DatagramChannel 用于 UDP 的数据读写 
      ServerSocketChannel/SocketChannel 用于 TCP 的数据读写
      
  • 选择器Selector

    • Selector常用方法介绍

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

      electionKey.isAcceptable(): 是否是连接继续事件
      SelectionKey.isConnectable(): 是否是连接就绪事件
      SelectionKey.isReadable(): 是否是读就绪事件
      SelectionKey.isWritable(): 是否是写就绪事件
      
    • SelectionKey中定义的4种事件

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

NIO是 面向缓冲区编程的。数据读取到一个缓冲区中,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。

NIO 三大核心原理示意图

image-20211213152210075

  1. 每个 channel 都会对应一个 Buffer
  2. Selector 对应一个线程, 一个线程对应多个 channel(连接)
  3. 每个 channel 都注册到 Selector选择器上
  4. Selector不断轮询查看Channel上的事件, 事件是通道Channel非常重要的概念
  5. Selector 会根据不同的事件,完成不同的处理操作
  6. Buffer 就是一个内存块 , 底层是有一个数组
  7. 数据的读取写入是通过 Buffer, 这个和 BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是
    NIO 的 Buffer 是可以读也可以写 , channel 是双向的.

2、NIO编写demo

Socket 客户端

public static void main(String[] args) throws IOException {
        // 1、创建客户端链接
        SocketChannel channel = SocketChannel.open();
        //2、绑定ip 端口号
        channel.connect(new InetSocketAddress("127.0.0.1", 1900));
        //3、想服务端写数据
        channel.write(ByteBuffer.wrap("惨死的呢".getBytes(StandardCharsets.UTF_8)));
        //获取服务端响应
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        System.out.println("获取服务端响应:" + new String(allocate.array(), 0, channel.read(allocate), StandardCharsets.UTF_8));
        channel.close();
    }

Socket 服务端

 public static void main(String[] args) throws IOException {
        //1. 打开一个服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 绑定对应的端口号
        serverSocketChannel.bind(new InetSocketAddress(1900));
        //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 selectionKey = iterator.next();
                //9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
                if (selectionKey.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端已连接>>>");
                    //必须设置通道为非阻塞, 因为selector需要轮询监听每个通道的事件
                    socketChannel.configureBlocking(false);
                    //指定监听事件为OP_READ
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                //10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
                if (selectionKey.isReadable()) {
                    //11. 得到客户端通道,读取数据到缓冲区
                    SocketChannel socketChannel = (SocketChannel) selectionKey.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();
            }
        }
    }
posted @ 2021-12-13 15:28  太阳SUNG  阅读(175)  评论(0编辑  收藏  举报