Netty Bytebuf解析
Posted HelloWorld搬运工
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty Bytebuf解析相关的知识,希望对你有一定的参考价值。
网络传输的基本单位是字节。Java NIO 提供了ByteBuffer作为它的容器,但是这个类使用起来比较复杂和麻烦。Netty提供了一个更好的实现:ByteBuf。
ByteBuf的API
Netty为数据处理提供的API通过抽象类ByteBuf和接口ByteBufHolder暴露出来。
下面列出ByteBuf API的优点:
可扩展到用户定义的buffer类型中
通过内置的复合buffer类型实现透明的零拷贝(zero-copy)
容量可以根据需要扩展
切换读写模式不需要调用ByteBUffer.flip()方法
读写采用不同的索引
支持方法链接调用
支持引用计数
支持池技术(比如:线程池、数据库连接池)
ByteBuf类—Netty的数据容器
因为所有的网络通信都涉及到字节序列的移动,一个有效而易用的数据结构是非常必要的。Netty的ByteBuf实现达到并超过这些需求。下面了解一下如何通过索引来简化对获取它持有数据的操作。
工作原理
ByteBuf维护两个不同的索引:读索引和写索引。当你从ByteBuf中读,它的readerIndex增加了读取的字节数;同理,当你向ByteBuf中写,writerIndex增加。下图显示了一个空的ByteBuf的布局和状态:
为了理解这些索引的关系,考虑一下如果你读数据时readerIndex已经和writerIndex一样会发生什么。在这个点,你已经读完了可读的数据。尝试继续往下读会引发一个IndexOutOfBoundsException,就像引发数组越界那样。
ByteBuf中名称以read或write开头的方法会推进相应的索引,而以set或get开头的不会。
可以指定ByteBuf最大的容量,默认是Integer.MAX_VALUE。
ByteBuf的使用模式
为了了解它的使用模式,我们得首先记住上图所展示的内容—一个数组以及两个索引来控制读和写。
堆缓冲区(HEAP BUFFER)
最常使用的ByteBuf模式将数据保存到JVM的堆中。被称为支持数组(backing array),这个模式提供了在没有使用池技术的情况下快速分配和释放(在堆缓冲区中)。这种方法是非常适合于来处理内置数据(legacy data,直译遗留数据,个人觉得这里翻译为内置数据更好理解一些,如果不合适,以后再改)的,如下所示:
ByteBuf heapBuf = ...; if (heapBuf.hasArray()){//检查是否有支持数组 byte[] array = heapBuf.array(); //得到支持数组 int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();//计算第一个字节的偏移量 int length = heapBuf.readableBytes();//计算可读字节数 handleArray(array, offset, length); //调用你的方法来处理这个array } 当hasArray()返回false时尝试访问支持数组会抛出UnsupportedOperationException。这个用法与JDK的ByteBuffer类似
直接缓冲区(DIRECT BUFFER)
我们认为创建对象时内存总是从堆中分配?但并非总是如此。在JDK1.4中引入的NIO的ByteBuffer类允许JVM 通过本地(native)方法调用分配内存,其目的是通过免去中间交换的内存拷贝, 提升IO处理速度。
直接缓冲区的内容可以驻留在垃圾回收扫描的堆区以外。这就解释了为什么直接缓冲区数据对网络数据传输来说是一种非常理想的方式。如果你的数据是存放在堆中分配的缓冲区,那么实际上,在通过 socket 发送数据之前,JVM需要将先数据复制到直接缓冲区。
这种方式的主要缺点是对于分配和释放内存空间来说比堆缓冲区消耗更大。如果你要处理内置数据的代码时可能会遇到另一个缺点:因为数据没有被分配到堆上,你可能需要做一个拷贝,如下所示:
ByteBuf directBuf = ... if (!directBuf.hasArray()) {//false表示为这是直接缓冲 int length = directBuf.readableBytes();//得到可读字节数 byte[] array = new byte[length]; //分配一个具有length大小的数组 directBuf.getBytes(directBuf.readerIndex(), array); //将缓冲区中的数据拷贝到这个数组中 handleArray(array, 0, length); //下一步处理 }
明显这要比使用支持数组的方式需要更多的工作,所以你如果提前知道数据会以一个数组的方式存取,推荐你使用堆内存。
复合缓冲区(COMPOSITE BUFFER)
第三种也是最后一种模式使用一个复合缓冲区,为多个ByteBuf提供一个组合的视图。你可以添加或者删除ByteBuf实例,一种JDK ByteBuffer中完全没有的实现。
Netty通过ByteBuf的子类-CompositeByteBuf来实现这种模式,提供了将多个buffer虚拟成一个合并的Buffer的技术。
注意:CompositeByteBuf中的ByteBuf实例可能同时包含堆缓冲区的和直接缓冲区的。如果CompositeByteBuf只含有一个实例,调用hasArray()方法会返回这个实例的hasArray()方法的值;否则总是返回false。
让我们考虑一条由两部分组成的消息,header和body,通过HTTP传输。这两部分由不同的应用程序模块产生,这个应用有为多条消息重用同一个body的选项。在这种情况下,会为每条消息创建一个新的header。
因为你不想为每条消息的两个buffer都重新分配空间,CompositeByteBuf就完美适合这种情况。
下图显示了这个消息的布局:
下面先介绍如何通过JDK的ByteBuffer来实现这个需求:
//通过一个数组来存储这条消息 ByteBuffer [] message = new ByteBuffer[]{header,body}; //使用副本来合并这两个部分 ByteBuffer message2 =ByteBuffer.allocate(header.remaining() + body.remaining()); message2.put(header); message2.put(body); message2.flip()
这种分配和拷贝的方式显然是低效且不合适的。下面介绍如何通过CompositeByteBuf来实现:
CompositeByteBuf messageBuf = Unpooled.compositeBuffer(); ByteBuf headerBuf = ...; //直接缓冲或堆缓冲都可 ByteBuf bodyBuf = ...; // 直接缓冲或堆缓冲都可 messageBuf.addComponents(headerBuf, bodyBuf);//将ByteBuf实例添加到CompositeByteBuf中 ..... messageBuf.removeComponent(0); //删除header for (ByteBuf buf : messageBuf) {//遍历messageBuf中的ByteBuf System.out.println(buf.toString()); }
CompositeByteBuf可能不允许访问支持数组,所以访问CompositeByteBuf中的数据的方式类似于直接缓冲区模式,如下所示:
CompositeByteBuf compBuf = Unpooled.compositeBuffer(); int length = compBuf.readableBytes();//得到可读的字节数 byte[] array = new byte[length];//分配一个字节数组 compBuf.getBytes(compBuf.readerIndex(), array);//将数据读到这个字节数组中 handleArray(array, 0, array.length);
Netty通过CompositeByteBuf来优化socket的IO操作,尽可能的消除JDK buffer实现中的性能和内存使用中的不足。尽管这些优化被封装到Netty的核心代码中,但你应该意识到这些优化的影响。
以上是关于Netty Bytebuf解析的主要内容,如果未能解决你的问题,请参考以下文章