搜文章
推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > HelloWorld搬运工 > Netty Bytebuf解析

Netty Bytebuf解析

HelloWorld搬运工 2018-10-31

网络传输的基本单位是字节。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解析》的版权归原作者「HelloWorld搬运工」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注HelloWorld搬运工微信公众号

HelloWorld搬运工微信公众号:jishubyg

HelloWorld搬运工

手机扫描上方二维码即可关注HelloWorld搬运工微信公众号

HelloWorld搬运工最新文章

精品公众号随机推荐