Netty in Action (十二) 第五章节 第一部分 简介ByteBuf

第五章 ByteBuf(分四部分翻译


本章节包括:

1)ByteBuf------Netty的数据容器

2)API介绍

3)使用案例

4)内存分配


我们之前提到过很多次,网络传输数据的最基本的数据单元是byte,Java的NIO提供了ByteBuffer作为字节的容器,但是这个类的使用有些过于复杂和麻烦


Netty对ByteBuffer提供了一个可选方案ByteBuf,一个很好的解决方案,解决了JDK原生的ByteBuffer的API使用不易的问题,同时ByteBuf为应用程序开发者提供了一系列的更好用的API


在这个章节中,我们以与JDK的ByteBuffer对比的方式介绍ByteBuf的最主要的功能和灵活性,这也会让你加深Netty对数据处理整体思路的理解,让你为第六章节讲解ChannelPipeline和ChannelHandler做好准备


5.1 The ByteBuf API


Netty对数据的处理的API通过两个组件去暴露-------抽象类ByteBuf和ByteBufHolder


以下是ByteBuf的API的一些优点:

1)对于用户自定义的buffer类型是可扩展的

2)对于透明的zero-copy可以通过内置的符合buffer类型可以达到你的要求

3) ByteBuf的容量如JDK的StringBuffer一样可以按照需要扩展

4)切换读写模式不需要调用ByteBuffer的flip方法

5)读和写有自己的独有索引下标

6)支持方法链式调用

7)支持引用计数

8)支持pooling


还有一些其他的相关类可以被用来管理ByteBuf实例的分配,这些类可以被用来提供对容器里的数据各种各样的操作,我们将在讲解ByteBuf和ByteBufHolder的时候探索这些特性


5.2 Class ByteBuf—Netty’s data container


因为所有的网络传输都需要byte的序列化后的移动,所以一个高效且易用的传输数据结构模型对于网络传输来说就是必不可少了,Netty的ByteBuf的实现就满足且超过了这些要求,让我们从看Netty如何使用索引来简单地获取ByteBuf中的数据来开始我们的学习吧~


5.2.1 How it works


ByteBuf有两个索引,一个用来读一个用来写,当你从ByteBuf中读取数据的时候,ByteBuf的readerIndex会随着被读取的字节的数量的增加而递增,与读相同,当你写一个字节的数据,那么ByteBuf的writerIndex也会递增加一,图5.1向你展示了一个空的ByteBuf的结构布局


为了理解这两个索引之间的关系,你看可以思考一下出现下面情形的时候会发生什么:当你读取数据的时候,读的readerIndex索引下标已经与写的writerIndex的下标值一样了,可以这么理解,在这个时候,你已经达到了可读的最大下标了,如果你接着尝试去读取更多的内容的时候,你会触发一个IndexOutOfBoundException的异常,与你操作数据最后一个元素的下一个元素一样


Netty in Action (十二) 第五章节 第一部分 简介ByteBuf_第1张图片


ByteBuf中以“read”和“write"名字为开头的方法都会相应的增加其对应的索引下标值,然而名字以”set“和”get“开头的方法则不会改变下标值,后者的方法需要将一个下标值以参数的方式传递给方法操作相关的数据


ByteBuf的最大容量是可以限制的(默认值是Integer.MAX_VALUE),如果尝试增加写的索引下标超过这个最大值的话,会触发一个异常


5.2.2 ByteBuf usage patterns


使用Netty的时候,你会碰到一些常用的构建于ByteBuf的常用的普通的使用模式,通过我们对它的分析,我们会把图5.1展示的模型牢记心中,一个字节数组,这个数组带有两个不同的索引来分别控制读和写


HEAP BUFFERS

使用ByteBuf模式存储数据的最最常用的方式是将数据存储在JVM的堆空间,被称之为“backing array”,这种方式提供了快速分配和在堆中对象无用的时候取消分配的功能,代码清单5.1向你展示了这个方法,这个方法同样适用于处理传统数据的情形

Netty in Action (十二) 第五章节 第一部分 简介ByteBuf_第2张图片

注意当使用hasArray()方法的时候来尝试获取backing array的时候,也能回返回false,可能会触发一个UnsupportedOperationException的异常,这个模式与JDK的ByteBuffer的用法一样


DIRECT BUFFERS

Direct buffer是另一个ByteBuf模式,我们希望为创建的对象分配的内存都来自于堆,这个模式可以使我们不需要必须使用JDK1.4 NIO中ByteBuffer类来允许JVM来通过原生的方法调用分配内存,这样做的目的是禁止在每一个IO操作上复制一个buffer的内容到中间状态的buffer上


ByteBuffer的Java的文档陈述的很清楚“直接缓冲区处于正常垃圾回收区之外”,这就解释了为什么直接缓冲区对于网络数据传输是理想的,如果你的数据是被装载在堆分配的buffer中的时候,在你将其通过socket发送之前JVM会将你的buffer复制到堆外的direct buffer中


这个最主要的缺点就是direct buffer它们有时候比堆内buffer分配和释放内存浪费更多的性能,如果你写过一些过去的旧式代码你也许会遇到这个缺点,因为数据并不在堆区,你可能需要做个复制,如下面代码清单所示:


很清楚,这比使用backing buffer多了一些操作,所以如果你能提前知道在这个direct buffer中的数据可以被作为一个array获取到的话,你可能会选择堆内存


COMPOSITE BUFFERS

第三种也是终极的模式就是composite buffer,这也是前两种模式的一种聚合模式,在这里你可以按照你的需要增加或者删除ByteBuf实例,这种特性在JDK的ByteBuffer实现中完全缺失


Netty用ByteBuf的子类CompositeByteBuf提供了这种多元buffer(可做单个buffer亦可作为合并buffer)实际代表


注意ByteBuf在CompositeByteBuf的实例可能会包括直接和非直接分配两种模式,如果它只是一个实例,那么调用CompositeByteBuf的hasArray()方法,会返回对应的那部分组件,如果没有则会返回false


为了说明,我们可以思考有这么一个对象,它由两部分组成,header和body,将由Http协议传输,这两个部分是由不同的应用模块产生的然后将其组装成信息然后发送出去,这个应用还有这样的需求:在多个消息中,不同的header可以对应同一个body,如果有这样的需求发生,那么你需要为每一个消息创建一个header


因为我们不需要为每一个消息分配两个部分的内存(body部分不需要多次分配),那么CompositeByteBuf这个对象很适合使用,通过使用CompositeByteBuf暴露的API,可以消除很多不必要的复制,图5.2展示了结果信息的结构


Netty in Action (十二) 第五章节 第一部分 简介ByteBuf_第3张图片

下面的代码清单向你展示了如果使用JDK的原生API是如何实现这个功能的:创建ByteBuffer的数组然后用数组存放信息的组件,然后创建第三个复制所有的数据到第三个ByteBuffer中

Netty in Action (十二) 第五章节 第一部分 简介ByteBuf_第4张图片

分配和赋值的操作,包括对数据管理的需要,使这个版本的实现看起来有点尴尬,且有些低效,下面的代码清单向你展示了使用CompositeByteBuf实现的版本

CompositeByteBuf中可能不允许直接获取backing array,所以想要像direct buffer模式一样获取数据,如下面的代码清单所示:

注意Netty使用了CompositeByteBuf优化了socket的I/O,消除了在JDK的buffer实现中出现的内存和性能损耗的,这个优化是在Netty核心内部代码实现的,并没有暴露出来,但是你必须意识到它带给你的好处


COMPOSITEBYTEBUF API CompositeByteBuf除了从ByteBuf中继承的方法之外,它还额外提供了一大堆额外的方法,详细可以参考java的文档


 

你可能感兴趣的:(netty,ByteBuf)