NIO非阻塞网络编程三大核心理念

本文详细介绍了NIO的三大核心组件。Buffer缓冲区本质是可读写的内存块,使用时有特定步骤,有不同内存类型;Channel通道可创建网络连接和读取数据,涵盖多种类型,与标准IO stream操作有区别;Selector选择器能检查通道状态,实现单线程管理多网络连接,基于事件驱动机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NIO三大核心组件:
1. Buffer缓冲区
2. Channel通道
3. Selector选择器
  • Buffer缓冲区
    缓冲区本质上是一个可以写入数据的内存块(类似于数组),然后可以再次读取。此内存块包含在NIO Buffer对象中,改对象提供了一组方法,可以更轻松的使用内存块。
    相比较直接对数组的操作,BufferAPI更容易操作和管理
    使用Buffer进行数据写入与读取,需要进行如下四个步骤:

    1. 将数据写入缓冲器
    2. 调用buffer.flip(),转换为读取模式
    3. 缓冲区读取数据
    4. 调用buffer.clear()或buffer.compact()清除缓冲去。
  • Buffer工作原理
    Buffer三个重要属性

    1. capacity容量:作为一个内存块,buffer具有一定的大小,也成为“容量”。
    2. position位置:写入模式时代表写数据的位置。读取模式时代表读取数据的位置。
    3. limit:写入模式,限制等于buffer容量。读取模式下,limit等于写入的数量。
      在这里插入图片描述
    public static void main(String[] args) {
    
        // 构建一个byte字节缓冲区,容量是4
        //         ByteBuffer byteBuffer = ByteBuffer.allocate(4);
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4);
        // 默认写入模式,查看三个重要的指标
        System.out.println(String.format("初始化:capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
                byteBuffer.position(), byteBuffer.limit()));
        // 写入2字节的数据
        byteBuffer.put((byte) 1);
        byteBuffer.put((byte) 2);
        byteBuffer.put((byte) 3);
        // 再看数据
        System.out.println(String.format("写入3字节后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
                byteBuffer.position(), byteBuffer.limit()));
    
        // 转换为读取模式(不调用flip方法,也是可以读取数据的,但是position记录读取的位置不对)
        System.out.println("#######开始读取");
        byteBuffer.flip();
        byte a = byteBuffer.get();
        System.out.println(a);
        byte b = byteBuffer.get();
        System.out.println(b);
        System.out.println(String.format("读取2字节数据后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
                byteBuffer.position(), byteBuffer.limit()));
    
        // 继续写入3字节,此时读模式下,limit=3,position=2.继续写入只能覆盖写入一条数据
        // clear()方法清除整个缓冲区。compact()方法仅清除已阅读的数据。转为写入模式
        byteBuffer.compact(); // buffer : 1 , 3
        byteBuffer.put((byte) 3);
        byteBuffer.put((byte) 4);
        byteBuffer.put((byte) 5);
        System.out.println(String.format("最终的情况,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
                byteBuffer.position(), byteBuffer.limit()));
    
        // rewind() 重置position为0
        // mark() 标记position的位置
        // reset() 重置position为上次mark()标记的位置
    
    }
    
  • BateBuffer内存类型
    ByteBuffer为性能关键型代码提供了直接内存(direct堆外)和非直接内存(heap堆)两种实现
    堆外内存获取方式:ByteBuffer byteBuffer = ByteBuffer.allocateDirect(noBytes);

    好处:
    1. 进行网络IO或者文件IO时比heapBuffer少一次拷贝。(file/socket----OS memory----jvm heap)GC或移动对象内存,在写file或socket的过程中,JVM的实现中,会先把数据复制到堆外,在进行写入。
    2. GC范围之外,降低GC压力,但实现了自动管理。DirectByteBuffer中有一个Cleaner对象(PhantomReference),Cleaner被GC前会执行clean方法,触发DirectByteBuffer中定义的Deallocator

    建议:

    1. 性能确实客观的时候才去使用;分配给大型、长寿命;(网络传输、文件读写场景)
    2. 通过虚拟机参数MaxDirectMemorySize限制大小,防止耗尽整个机器的内存
  • Channel 通道 (通道可以创建网络连接,也可以用来读取数据)
    Channel的API涵盖了UDP/TCP网络和文件

    1. FileChannel
    2. DatagramChannel
    3. SocketChannel
    4. ServerSocketChannel

和标准IO stream操作的区别:
在一个通道内进行读取和写入,stream通常是单向的(input或output)可以肥阻塞读取和写入通道,通道始终读取或写入缓冲区。

  • SocketChannel
    SocketChannel用于简历TCP网络连接,类似于java.net.Socket。有两种创建SocketChannel形式:

    1. 客户端主动发起和服务器的链接
    2. 服务端获取的新连接
            // 客户端主动发起连接的方式
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false); // 设置为非阻塞模式
        socketChannel.connect(new InetSocketAddress("www.baidu.com", 80));
        socketChannel.write(byteBuffer); // 发送请求数据-->向通道中写数据
        int bytesRead = socketChannel.read(byteBuffer); // 读取服务端返回-->读取缓冲区的数据
        socketChannel.close();
    

    write写:write()在尚未写入任何内容时就可能返回了。需要在循环中调用write()。
    read读:read()方法可能直接返回而根本读取不到任何数据,根据返回的int值判断读取了多少字节。

  • ServerSocketChannel
    ServerSocketChannel 可以监听新建的TCP连接通道,类似于SocketChannel。

            // 创建网络服务器
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);// 设置为非阻塞模式
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));// 绑定端口
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();// 获取新tcp连接通道
            if (socketChannel != null){
                // tcp请求 读取/相应
            }
        }
    

    serverSocketChannel.accept():如果该通道处于非阻塞模式,那么如果没有挂起的连接,该方法将立即返回null。必须检查返回的SocketChannel是否为null;

  • Selector 选择器
    Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。实现单个线程可以管理对个网络连接。
    一个线程使用Selector监听多个channel的不同事件:
    四个事件分别对应SelectionKey四个常量。

    1. Connect 连接(SelectionKey.OP_CONNECT)
    2. Accept准备就绪(OP_ACCEPT)
    3. Read读取(OP_READ)
    4. Write(OP_WRITE)

实现一个线程处理多个通道的核心概念理解:事件驱动机制。
非阻塞的网络通道下,开发者通过Selector注册对于通道感兴趣的事件类型,线程通过监听事件来触发相应的代码执行。(拓展:更底层是操作系统的多路复用机制)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值