DotNetty完全教程(四)
Excerpt
ByteBufferNetty中ByteBuffer的介绍Netty 的数据处理 API 通过两个组件暴露——abstract class ByteBuf 和 interfaceByteBufHolderDotNetty中有AbstractByteBuffer IByteBuffer IByteBufferHolder优点:它可以被用户自定义的缓冲区类型扩展;通过内置的复合缓冲区…
ByteBuffer
Netty中ByteBuffer的介绍
Netty 的数据处理 API 通过两个组件暴露——abstract class ByteBuf 和 interface
ByteBufHolder
DotNetty中有AbstractByteBuffer IByteBuffer IByteBufferHolder
优点:
- 它可以被用户自定义的缓冲区类型扩展;
- 通过内置的复合缓冲区类型实现了透明的零拷贝;
- 容量可以按需增长(类似于 JDK 的 StringBuilder);
- 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法;
- 读和写使用了不同的索引;
- 支持方法的链式调用;
- 支持引用计数;
- 支持池化
原理:
每一个ByteBuf都有两个索引,读索引和写索引,read和write会移动索引,set和get不会引动索引。
使用ByteBuf
- 堆缓冲区(使用数组的方式展示和操作数据)
使用支撑数组给ByteBuf提供快速的分配和释放的能力。适用于有遗留数据需要处理的情况。
1 | public override void ChannelRead(IChannelHandlerContext ctx, object msg) |
- 直接缓冲区
1 | public override void ChannelRead(IChannelHandlerContext ctx, object msg) |
- CompositeByteBuffer 复合缓冲区
如果要发送的命令是由两个ByteBuf拼接构成的,那么就需要复合缓冲区,比如Http协议中一个数据流由头跟内容构成这样的逻辑。
1 | public override void ChannelRead(IChannelHandlerContext ctx, object msg) |
字节级操作
- 读取(不移动索引)
1 | public override void ChannelRead(IChannelHandlerContext ctx, object msg) |
- 丢弃可丢弃字节
所谓可丢弃字节就是调用read方法之后,readindex已经移动过了的区域,这段区域的字节称为可丢弃字节。
1 | message.DiscardReadBytes(); |
只有在内存十分宝贵需要清理的时候再调用这个方法,随便调用有可能会造成内存的复制,降低效率。
3. 读取所有可读字节(移动读索引)
1 | while (message.IsReadable()) |
- 写入数据
1 | // 使用随机数填充可写区域 |
- 管理索引
- MarkReaderIndex ResetReaderIndex 标记和恢复读索引
- MarkWriterIndex ResetWriterIndex 标记和恢复写索引
- SetReaderIndex(int) SetWriterIndex(int) 直接移动索引
- clear() 重置两个索引都为0,但是不会清除内容
- 查找
- IndexOf()
- 使用Processor
1 | // 查找\r |
- 派生
派生的意思是创建一个新的ByteBuffer,这个ByteBuf派生于其他的ByteBuf,派生出来的子ByteBuf具有自己的读写索引,但是本质上指向同一个对象,这样就导致了改变一个,另一个也会改变。
- duplicate();
- slice();
- slice(int, int);
- Unpooled.unmodifiableBuffer(…);
- order(ByteOrder);
- readSlice(int)。
- 复制
复制不同于派生,会复制出一个独立的ByteBuf,修改其中一个不会改变另一个。
- copy
- 释放
1 | // 显式丢弃消息 |
- 增加引用计数防止释放
1 | ReferenceCountUtil.retain(message) |
- 其他api
ByteBufHolder
- 目的
再数据处理的过程中不仅仅有字节数据内容本身,还会有一些附加信息,比如HTTP响应的状态码,Cookie等。给ByteBuf附加信息就要用到ByteBufHolder. - API
管理ByteBuffer
按需分配 ByteBufAllocator
注意分配是池化的,最大程度上降低分配和释放内存的开销。1
2
3
4
5
6
7// 获取Allocator
// 1
IChannelHandlerContext ctx = null;
IByteBufferAllocator allocator = ctx.Allocator;
// 2
IChannel channel = null;
allocator = channel.Allocator;
有两种ByteBufAllocator的实现:PooledByteBufAllocator和UnpooledByteBufAllocator,前者池化了ByteBuf的实例,极大限度的提升了性能减少了内存碎片。
2. Unpooled缓冲区
获取不到 ByteBufAllocator的引用的时候我们可以使用Unpooled工具类来操作ByteBuf。
- ByteBufUtil
这个类提供了一些通用的API,都是静态的辅助方法,例如hexdump方法可以以十六进制的方式打印ByteBuf的内容。还有equal方法判断bytebuf是否相等。
引用计数
目的
ByteBuf和ByteBufHolder都有计数的机制。引用计数都从1开始,如果计数大于0则不被释放,如果等于0就会被释放。它的目的是为了支持池化的实现,降低了内存分配的开销。
异常
如果访问一个计数为0的对象就会引发IllegalReferenceCountException。