netty入门-7 ByteBuf

前言

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 indexcapacity之间表示可写字节。
还有就是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的还有直接内存一块写一下,加深下理解。

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值