netty---ByteBuf

ByteBuf
ByteBuf API 的优点:
它可以被用户自定义的缓冲区类型扩展;
通过内置的复合缓冲区类型实现了透明的零拷贝;
容量可以按需增长(类似于 JDK 的 StringBuilder) ;
在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法;
读和写使用了不同的索引;
支持方法的链式调用;
支持引用计数;
支持池化。
ByteBuf 维护了两个不同的索引, 名称以 read 或者 write 开头的 ByteBuf 方法, 将会
推进其对应的索引, 而名称以 set 或者 get 开头的操作则不会。
如果打算读取字节直到 readerIndex 达到和 writerIndex 同样的值时会发生什么。 在那
时, 你将会到达“可以读取的” 数据的末尾。 就如同试图读取超出数组末尾的数据一样, 试
图读取超出该点的数据将会触发一个 IndexOutOf-BoundsException。
可以指定 ByteBuf 的最大容量。 试图移动写索引(即 writerIndex) 超过这个值将会触发
一个异常。 (默认的限制是 Integer.MAX_VALUE。 )
使用模式
堆缓冲区
      最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中。 这种模式被称为支撑数组
(backing array) , 它能在没有使用池化的情况下提供快速的分配和释放。 可以由 hasArray()
来判断检查 ByteBuf 是否由数组支撑。 如果不是, 则这是一个直接缓冲区。
直接缓冲区
     直接缓冲区是另外一种 ByteBuf 模式。
直接缓冲区的主要缺点是, 相对于基于堆的缓冲区, 它们的分配和释放都较为昂贵。
复合缓冲区
    复合缓冲区 CompositeByteBuf, 它为多个 ByteBuf 提供一个聚合视图。 比如 HTTP 协议,
分为消息头和消息体, 这两部分可能由应用程序的不同模块产生, 各有各的 ByteBuf, 将会
在消息被发送的时候组装为一个 ByteBuf, 此时可以将这两个 ByteBuf 聚合为一个
CompositeByteBuf, 然后使用统一和通用的 ByteBuf API 来操作。
分配
如何在我们的程序中获得 ByteBuf 的实例, 并使用它呢? Netty 提供了两种方式
ByteBufAllocator 接口
Netty 通过 interface ByteBufAllocator 分配我们所描述过的任意类型的 ByteBuf 实例。

netty---ByteBuf

netty---ByteBuf

可以通过 Channel(每个都可以有一个不同的 ByteBufAllocator 实例) 或者绑定到
ChannelHandler 的 ChannelHandlerContext 获取一个到 ByteBufAllocator 的引用。
netty---ByteBuf

Netty 提供了两种 ByteBufAllocator 的实现: PooledByteBufAllocator 和
Unpooled-ByteBufAllocator。 前者池化了 ByteBuf 的实例以提高性能并最大限度地减少内存碎
片。 后者的实现不池化 ByteBuf 实例, 并且在每次它被调用时都会返回一个新的实例。
Netty4.1 默认使用了 PooledByteBufAllocator。
Unpooled 缓冲区
Netty 提供了一个简单的称为 Unpooled 的工具类, 它提供了静态的辅助方法来创建未
池化的 ByteBuf 实例。
buffer() 返回一个未池化的基于堆内存存储的 ByteBuf
directBuffer()返回一个未池化的基于直接内存存储的 ByteBuf
wrappedBuffer() 返回一个包装了给定数据的 ByteBuf
copiedBuffer() 返回一个复制了给定数据的 ByteBuf
Unpooled 类还可用于 ByteBuf 同样可用于那些并不需要 Netty 的其他组件的非网络项目
随机访问索引/顺序访问索引/读写操作
如同在普通的 Java 字节数组中一样, ByteBuf 的索引是从零开始的: 第一个字节的索
引是 0, 最后一个字节的索引总是 capacity() - 1。 使用那些需要一个索引值参数(随机访问,
也即是数组下标)的方法(的其中) 之一来访问数据既不会改变 readerIndex 也不会改变
writerIndex。 如果有需要, 也可以通过调用 readerIndex(index)或者 writerIndex(index)来手动
移动这两者。 顺序访问通过索引访问
有两种类别的读/写操作:
get()和 set()操作, 从给定的索引开始, 并且保持索引不变; get+数据字长
(bool.byte,int,short,long,bytes)
read()和 write()操作, 从给定的索引开始, 并且会根据已经访问过的字节数对索引
进行调整。
更多的操作
isReadable() 如果至少有一个字节可供读取, 则返回 true
isWritable() 如果至少有一个字节可被写入, 则返回 true
readableBytes() 返回可被读取的字节数
writableBytes() 返回可被写入的字节数
capacity() 返回 ByteBuf 可容纳的字节数。 在此之后, 它会尝试再次扩展直到达到
maxCapacity()
maxCapacity() 返回 ByteBuf 可以容纳的最大字节数
hasArray() 如果 ByteBuf 由一个字节数组支撑, 则返回 true
array() 如果 ByteBuf 由一个字节数组支撑则返回该数组; 否则, 它将抛出一个
UnsupportedOperationException 异常
可丢弃字节
可丢弃字节的分段包含了已经被读过的字节。 通过调用 discardReadBytes()方法, 可以丢
弃它们并回收空间。 这个分段的初始大小为 0, 存储在 readerIndex 中, 会随着 read 操作的
执行而增加(get*操作不会移动 readerIndex) 。
缓冲区上调用 discardReadBytes()方法后, 可丢弃字节分段中的空间已经变为可写的了。
频繁地调用 discardReadBytes()方法以确保可写分段的最大化, 但是请注意, 这将极有可能会
导致内存复制, 因为可读字节必须被移动到缓冲区的开始位置。 建议只在有真正需要的时候
才这样做, 例如, 当内存非常宝贵的时候。
 

netty---ByteBuf

可读字节
ByteBuf 的可读字节分段存储了实际数据。 新分配的、 包装的或者复制的缓冲区的默认
的 readerIndex 值为 0。
可写字节
可写字节分段是指一个拥有未定义内容的、 写入就绪的内存区域。 新分配的缓冲区的
writerIndex 的默认值为 0。 任何名称以 write 开头的操作都将从当前的 writerIndex 处开始
写数据, 并将它增加已经写入的字节数。
netty---ByteBuf

索引管理
调用 markReaderIndex()、 markWriterIndex()、 resetWriterIndex()和 resetReaderIndex()来
标记和重置 ByteBuf 的 readerIndex 和 writerIndex。
也可以通过调用 readerIndex(int)或者 writerIndex(int)来将索引移动到指定位置。 试图将
任何一个索引设置到一个无效的位置都将导致一个 IndexOutOfBoundsException。
可以通过调用 clear()方法来将 readerIndex 和 writerIndex 都设置为 0。 注意, 这并不会
清除内存中的内容。
查找操作
在 ByteBuf 中有多种可以用来确定指定值的索引的方法。最简单的是使用 indexOf()方法。
较复杂的查找可以通过调用 forEachByte()。
代码展示了一个查找回车符(\r) 的例子
 

netty---ByteBuf

派生缓冲区
派生缓冲区为 ByteBuf 提供了以专门的方式来呈现其内容的视图。 这类视图是通过以下
方法被创建的:
duplicate();
slice();
slice(int, int);
Unpooled.unmodifiableBuffer(…);
order(ByteOrder);
readSlice(int)。
每个这些方法都将返回一个新的 ByteBuf 实例, 它具有自己的读索引、 写索引和标记索
引。 其内部存储和 JDK 的 ByteBuffer 一样也是共享的。
ByteBuf 复制 如果需要一个现有缓冲区的真实副本, 请使用 copy()或者 copy(int, int)方
法。 不同于派生缓冲区, 由这个调用所返回的 ByteBuf 拥有独立的数据副本。
引用计数
引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持
有的资源来优化内存使用和性能的技术。Netty 在第 4 版中为 ByteBuf 引入了引用计数技术,
interface ReferenceCounted。
工具类
ByteBufUtil 提供了用于操作 ByteBuf 的静态的辅助方法。 因为这个 API 是通用的, 并
且和池化无关, 所以这些方法已然在分配类的外部实现。
这些静态方法中最有价值的可能就是 hexdump()方法, 它以十六进制的表示形式打印
ByteBuf 的内容。 这在各种情况下都很有用, 例如, 出于调试的目的记录 ByteBuf 的内容。
十六进制的表示通常会提供一个比字节值的直接表示形式更加有用的日志条目, 此外, 十六
进制的版本还可以很容易地转换回实际的字节表示。
另一个有用的方法是 boolean equals(ByteBuf, ByteBuf), 它被用来判断两个 ByteBuf 实例
的相等性
资源释放
当某个 ChannelInboundHandler 的实现重写 channelRead()方法时, 它要负责显式地释放
与池化的 ByteBuf 实例相关的内存。 Netty 为此提供了一个实用方法
ReferenceCountUtil.release()
Netty 将使用 WARN 级别的日志消息记录未释放的资源, 使得可以非常简单地在代码
中发现违规的实例。 但是以这种方式管理资源可能很繁琐。 一个更加简单的方式是使用
SimpleChannelInboundHandler, SimpleChannelInboundHandler 会自动释放资源。
1、 对于入站请求, Netty 的 EventLoo 在处理 Channel 的读操作时进行分配 ByteBuf, 对
于这类 ByteBuf, 需要我们自行进行释放, 有三种方式, 或者使用
SimpleChannelInboundHandler, 或者在重写 channelRead()方法使用
ReferenceCountUtil.release()或者使用 ctx.fireChannelRead 继续向后传递;
2、 对于出站请求, 不管 ByteBuf 是否由我们的业务创建的, 当调用了 write 或者
writeAndFlush 方法后, Netty 会自动替我们释放, 不需要我们业务代码自行释放。