文章目录
- 前言
- ByteBuf
- 结构
- 池化与非池化
- 创建(直接内存/堆内存)
- 写入和读取
- 释放
- 零拷贝,slice,duplicate, copy,Composite
 
- 结语
前言
ByteBuf这部分视频讲的更为详细。
 主要参考视频。
ByteBuf
结构
首先这个东西的结构与ByteBuffer十分相似。
 就是一个带指针的字节容器。
 
有读指针(read index),写指针(write index),和容量(capacity)。
 我们每读取1字节,read index就加1
 每写1字节,write index加1
 可读范围就是write index - read index,读指针到了写指针的位置就表明没有数据可读。
 read index之前一般就是已读字节
 write index如果没到capacity,则write index到capacity之间表示可写字节。
 还有就是ByteBuf可以扩容。内部有变量maxCapacity,若扩容时会超过这个值会报错。
池化与非池化
池化,听着有点陌生。
 数据库连接池,线程池。都是池化来达到重用的思路。
 ByteBuf也一样。
 正常非池化意味着每次使用都要创建ByteBuf实例。
 而池化则可以直接重用池中的ByteBuf实例。
 直接内存的申请与释放是比较消耗性能的。所以采用池化可以省掉这部分开销。且省掉申请空间的时间。
创建(直接内存/堆内存)
可以创建基于堆的和基于直接内存的ByteBuf。
//默认池化,基于直接内存
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
//池化,基于堆内存
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
//池化,基于直接内存
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
直接内存不受JVM GC管理,需要手动释放。
 直接内存申请释放比较消耗性能(所以推荐池化)。
 直接内存的优势在于读写性能高
写入和读取
这里的读取写入不需要切换读写模式
 读取的话同样有mark和reset方法
buffer.markReaderIndex();
buffer.resetReaderIndex();
buffer.getInt();//读取一个int值,不改变指针位置
基本使用和ByteBuffer一样,读写指针的变化这里类似于NIO的ByteBuffer。不再赘述
这里主要说一个问题。
 比如我们使用writeInt方法,int我们知道是32位的,4个字节。例如写入一个数字8,writeInt(8),那么我们如果读取使用readByte(),表示读一个字节,前三次就会读出0,第四次才会读出8,相当于你把32位的int值分四次,每次读8位即一个字节。使用readInt就会直接读32位,不会出现上面的问题。
 所以一般怎么写怎么读。
下面是老师讲义里的API表格
 读取的API类似
| 方法签名 | 含义 | 备注 | 
|---|---|---|
| writeBoolean(boolean value) | 写入 boolean 值 | 用一字节 01|00 代表 true|false | 
| writeByte(int value) | 写入 byte 值 | |
| writeShort(int value) | 写入 short 值 | |
| writeInt(int value) | 写入 int 值 | Big Endian,即 0x250,写入后 00 00 02 50 | 
| writeIntLE(int value) | 写入 int 值 | Little Endian,即 0x250,写入后 50 02 00 00 | 
| writeLong(long value) | 写入 long 值 | |
| writeChar(int value) | 写入 char 值 | |
| writeFloat(float value) | 写入 float 值 | |
| writeDouble(double value) | 写入 double 值 | |
| writeBytes(ByteBuf src) | 写入 netty 的 ByteBuf | |
| writeBytes(byte[] src) | 写入 byte[] | |
| writeBytes(ByteBuffer src) | 写入 nio 的 ByteBuffer | |
| int writeCharSequence(CharSequence sequence, Charset charset) | 写入字符串 | 
释放
我们使用直接内存的ByteBuf需要我们自己来释放
 Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口
- 每个 ByteBuf 对象的初始计数为 1
- 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
- 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
- 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用
我们一般ByteBuf会经过多个ChannelHandler的传递,所以我们不能直接
 release。而是在最后使用ByteBuf的地方释放。
 老师这里说的是谁是最后使用者,谁负责 release
 讲义的详细分析
- 起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
- 入站 ByteBuf 处理原则 
  - 对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
- 将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
- 如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
- 注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
- 假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
 
- 出站 ByteBuf 处理原则 
  - 出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
 
- 异常处理原则 
  - 有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true
 
零拷贝,slice,duplicate, copy,Composite
结语
ByteBuf是最后一部分。
 还有一个就是Netty提供了拆包器了来解决粘包半包问题。下篇来说
 协议设计放在下下篇。
再后面就是Netty的应用了。这块不一定会写,想看可以去看视频
 比如实现简单的客户端服务端消息收发,通信协议,实现一个聊天等应用。就是说基本的使用来实现我们的需求。
然后就是其实NIO部分的IO模型没写,因为这个属于网上能找到很多资料。
 NIO的零拷贝的逐步优化可能写一下,老师讲义里写的挺好的。
 Netty ByteBuf的零拷贝这里仅提了概念,后续写了NIO的和ByteBuf的还有直接内存一块写一下,加深下理解。



















