java网络编程—NIO与Netty(四) ByteBuffer数据处理与零拷贝

版权声明:坚持原创,坚持深入原理,未经博主允许不得转载!! https://blog.csdn.net/lemon89/article/details/77427338

相关文章
java网络编程—NIO与Netty(四)
java网络编程—NIO与Netty(三)
java网络编程—NIO与Netty(二)
java网络编程—NIO与Netty(一)
java网络编程—基石:五种IO模型及原理(多路复用\epoll)

数据容器——ByteBuf

JDK的NIO中使用ByteBuffer作为网络字节流传输的容器,与ByteBuffer类似,Netty中定义了ByteBuff作为数据容器。
ByteBuff使用更方便、功能更强大灵活、性能更好。

ByteBuff通过定义了两个索引(readIndex、writeIndex)对数据做管理,并且因为读写索引分离,不需要执行flip()操作。
这里写图片描述

ByteBuf——堆缓冲区

最常使用的ByteBuf将数据同个一个byte数组的方式存储在堆内存中,这种方式称为backing Array.

    /**
     * Backing array:使用一个byte[] array作为底层数据结构
     */
    public static void heapBuffer() {
        ByteBuf heapBuf = Unpooled.buffer(1024);
        if (heapBuf.hasArray()) {//判断heapBuf是否是backing Array类型
            byte[] array = heapBuf.array();//获取底层的数组
            int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
            int length = heapBuf.readableBytes();//获取可读取字节数量
            handleArray(array, offset, length);
        }
    }   }**

ByteBuf——直接缓存区

直接缓冲区是另外一种 ByteBuf 模式。NIO 在 JDK 1.4 中引入的ByteBuffer 类允许 JVM 实现通过本地调用来分配非堆的直接内存,不受GC管理。

为什么要使用直接内存(DirectMemory)
对于网络传输是最理想的选择。因为通常JVM处理网络程序时,会将数据从JVM的堆转移到非堆直接内存上,然后再发送到传输层等等。也就是说为了网络传输,通常要将堆内存转为非堆内存。那么我们直接使用非堆内存处理网络传输会更加高效。Netty的一种零拷贝

ByteBuf——复合缓冲区

CompositeByteBuf 实现了对两种模式的合并

public static void byteBufComposite() {
        CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
        ByteBuf headerBuf = Unpooled.copiedBuffer("header".getBytes()); // can be backing or direct
        ByteBuf bodyBuf = Unpooled.directBuffer(); // can be backing or direct
        bodyBuf.writeBytes("body".getBytes());
        messageBuf.addComponents(headerBuf, bodyBuf);
        messageBuf.removeComponent(0); // remove the header
        for (ByteBuf buf : messageBuf) {
            System.out.println(buf.toString() + "\n\r");

            while (buf.isReadable()) {
                System.out.print((char) buf.readByte());

            }
            // System.out.println(new String(buf.array()));
        }
    }

ByteBuf分配

ByteBufAllocator用来完成ByteBuf的分配初始化,实现了ByteBuf的池化。

PooledByteBufAllocator与UnpooledByteBufAllocator

Netty提供基于内存池的缓存区数据重用机制,相比于朝生熄灭的ByteBuffer来说,可以极大提高性能。

Netty提供了两种ByteBufAllocator的实现: PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。后者的实现不池化ByteBuf实例, 并且在每次它被调用时都会返回一个新的实例。

API简介:
返回一个基于堆或者直接内存存储的 ByteBuf

  • buffer()
  • buffer(int initialCapacity);
  • buffer(int initialCapacity, int maxCapacity);

返回一个基于堆内存存储的ByteBuf

  • heapBuffer()
  • heapBuffer(int initialCapacity)
  • heapBuffer(int initialCapacity, int maxCapacity)

返回一个基于直接内存存储的ByteBuf

  • directBuffer()
  • directBuffer(int initialCapacity)
  • directBuffer(int initialCapacity, int maxCapacity)

返回一个可以通过添加最大到指定数目的基于堆的或者直接内存存储的缓冲区来扩展的CompositeByteBu

  • compositeBuffer()
  • compositeBuffer(int maxNumComponents)
  • compositeDirectBuffer() compositeDirectBuffer(int maxNumComponents);
  • compositeHeapBuffer() compositeHeapBuffer(int maxNumComponents);

返回一个用于套接字的 I/O 操作的 ByteBuf
默认地,当所运行的环境具有 sun.misc.Unsafe 支持时,返回基于直接内存存储的 ByteBuf,否则返回基于堆内存存储的 ByteBuf;

  • ioBuffer()

读写访问

/**
     * <pre>
     * get()和 set()操作, 从给定的索引开始,并且保持索引不变;
     * 
     * <pre>
     * 
     * read()和 write()操作, 从给定的索引开始,并且会根据已经访问过的字节数对索 引进行调整
     */
    public static void byteBufSetGet() {
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
        System.out.println((char) buf.getByte(0));
        int readerIndex = buf.readerIndex();
        int writerIndex = buf.writerIndex();
        buf.setByte(0, (byte) 'B');
        System.out.println((char) buf.getByte(0));
        assert readerIndex == buf.readerIndex();
        assert writerIndex == buf.writerIndex();
    }

    public static void byteBufWriteRead() {
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
        System.out.println((char) buf.readByte());
        int readerIndex = buf.readerIndex();
        int writerIndex = buf.writerIndex();
        buf.writeByte((byte) '?');
        assert readerIndex == buf.readerIndex();
        assert writerIndex != buf.writerIndex();
    }

Netty默认使用了PooledByteBufAllocator可以从Channel中或者ChannelHandlerContext中获取ByteBuffAllocator的引用。

public static void obtainingByteBufAllocatorReference() {
        Channel channel = CHANNEL_FROM_SOMEWHERE; // get reference form somewhere
        ByteBufAllocator allocator = channel.alloc();
        // ...
        ChannelHandlerContext ctx = CHANNEL_HANDLER_CONTEXT_FROM_SOMEWHERE; // get reference form somewhere
        ByteBufAllocator allocator2 = ctx.alloc();
        // ...
    }

零拷贝

1.NIO中使用直接内存(Direct Memory)。
传统的数据发送,需要从JVM heap内存中拷贝数据到系统直接内存中,然后将直接内存的数据写入Socket。
直接使用DirectMemory,可以减少内存数据的拷贝。

2.Netty提供了组合Buffer对象。
Netty 的 Zero-copy 体现在如下几个个方面:

  • Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝.
  • 通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作.
  • ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝.
  • 通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题.

示例:transferTo/transferFrom具体示例

猜你喜欢

转载自blog.csdn.net/lemon89/article/details/77427338