本文内容主要参考<<Netty In Action>>,偏笔记向.

网络编程中,字节缓冲区是一个比较基本的组件.Java NIO提供了ByteBuffer,但是使用过的都知道ByteBuffer对于读写数据操作还是有些麻烦的,切换读写状态需要flip().Netty框架对字节缓冲区进行了封装,名称是ByteBuf,相较于ByteBuffer更灵活.

1.ByteBuf特点概览

  • 用户可以自定义缓冲区类型对其扩展
  • 通过内置的符合缓冲区类型实现了透明的零拷贝
  • 容量可以按需增长(类似StringBuilder)
  • 切换读写模式不用调用flip()方法
  • 读写使用各自的索引
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化

2.ByteBuf类介绍

2.1工作模式

ByteBuf维护了两个指针,一个用于读取(readerIndex),一个用于写入(writerIndex).

使用ByteBuf的API中的read*方法读取数据时,readerIndex会根据读取字节数向后移动,但是get*方法不会移动readerIndex;使用write*数据时,writerIndex会根据字节数移动,但是set*方法不会移动writerIndex.(read*表示read开头的方法,其余意义相同)

读取数据时,如果readerIndex超过了writerIndex会触发IndexOutOfBoundsException.

可以指定ByteBuf容量最大值,capacity(int)ensureWritable(int),当超出容量时会抛出异常.

2.2使用模式

2.2.1堆缓冲区

ByteBuf存入JVM的堆空间.能够在没有池化的情况下提供快速的分配和释放.

除此之外,ByteBuf的堆缓冲区还提供了一个后备数组(backing array).后备数组和ByteBuf中的数据是对应的,如果修改了backing array中的数据,ByteBuf中的数据是同步的.

public static void main(String[] args) {
ByteBuf heapBuf = Unpooled.buffer(1024);
if(heapBuf.hasArray()){
heapBuf.writeBytes("Hello,heapBuf".getBytes());
System.out.println("数组第一个字节在缓冲区中的偏移量:"+heapBuf.arrayOffset());
System.out.println("缓冲区中的readerIndex:"+heapBuf.readerIndex());
System.out.println("writerIndex:"+heapBuf.writerIndex());
System.out.println("缓冲区中的可读字节数:"+heapBuf.readableBytes());//等于writerIndex-readerIndex
byte[] array = heapBuf.array();
for(int i = 0;i < heapBuf.readableBytes();i++){
System.out.print((char) array[i]);
if(i==5){
array[i] = (int)'.';
}
}
//不会修改readerIndex位置
System.out.println("\n读取数据后的readerIndex:"+heapBuf.readerIndex());
//读取缓冲区的数据,查看是否将逗号改成了句号
while (heapBuf.isReadable()){
System.out.print((char) heapBuf.readByte());
}
}

输出:

数组第一个字节在缓冲区中的偏移量:0
缓冲区中的readerIndex:0
writerIndex:13
缓冲区中的可读字节数:13
Hello,heapBuf
读取数据后的readerIndex:0
Hello.heapBuf

如果hasArray()返回false,尝试访问backing array会报错

2.2.2直接缓冲区

直接缓冲区存储于JVM堆外的内存空间.这样做有一个好处,当你想把JVM中的数据写给socket,需要将数据复制到直接缓冲区(JVM堆外内存)再交给socket.如果使用直接缓冲区,将减少复制这一过程.

但是直接缓冲区也是有不足的,与JVM堆的缓冲区相比,他们的分配和释放是比较昂贵的.而且还有一个缺点,面对遗留代码的时候,可能不确定ByteBuf使用的是直接缓冲区还是堆缓冲区,你可能需要进行一次额外的复制.如代码示例.

与自带后备数组的堆缓冲区来讲,这要多做一些工作.所以,如果确定容器中的数据会被作为数组来访问,你可能更愿意使用堆内存.

		//实际上你不知道从哪获得的引用,这可能是一个直接缓冲区的ByteBuf
//忽略Unpooled.buffer方法,当做不知道从哪获得的directBuf
ByteBuf directBuf = Unpooled.buffer(1024);
//如果想要从数组中访问数据,需要将直接缓冲区中的数据手动复制到数组中
if (!directBuf.hasArray()) {
int length = directBuf.readableBytes();
byte[] array = new byte[length];
directBuf.getBytes(directBuf.readerIndex(), array);
handleArray(array, 0, length);
}
2.2.3符合缓冲区(CompositeByteBuf)

聚合缓冲区是个非常好用的东西,是多个ByteBuf的聚合视图,可以添加或删除ByteBuf实例.

CompositeByteBuf中的ByteBuf实例可能同事包含直接内存分配和非直接内存分配.如果其中只有一个实例,那么调用CompositeByteBuf中的hasArray()方法将返回该组件上的hasArray()方法的值,否则返回false

多个ByteBuf组成一个完整的消息是很常见的,比如headerbody组成的HTTP协议传输的消息.消息中的body有时候可能能重用,我们不想每次都创建重复的body,我们可以通过CompositeByteBuf来复用body.

对比一下JDK中的ByteBuffer实现复合缓冲区和Netty中的CompositeByteBuf.

//JDK版本实现复合缓冲区
public static void byteBufferComposite(ByteBuffer header, ByteBuffer body) {
//使用一个数组来保存消息的各个部分
ByteBuffer[] message = new ByteBuffer[]{ header, body }; // 创建一个新的ByteBuffer来复制合并header和body
ByteBuffer message2 =
ByteBuffer.allocate(header.remaining() + body.remaining());
message2.put(header);
message2.put(body);
message2.flip();
} //Netty中的CompositeByteBuf
public static void byteBufComposite() {
CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
ByteBuf headerBuf = Unpooled.buffer(1024); // 可能是直接缓存也可能是堆缓存中的
ByteBuf bodyBuf = Unpooled.buffer(1024); // 可能是直接缓存也可能是堆缓存中的
messageBuf.addComponents(headerBuf, bodyBuf);
//...
messageBuf.removeComponent(0); // remove the header
for (ByteBuf buf : messageBuf) {
System.out.println(buf.toString());
}
}

CompositeByteBuf不支持访问其后备数组,所以访问CompositeByteBuf中的数据类似于访问直接缓冲区

CompositeByteBuf compBuf = Unpooled.compositeBuffer();
int length = compBuf.readableBytes();
byte[] array = new byte[length];
//将CompositeByteBuf中的数据复制到数组中
compBuf.getBytes(compBuf.readerIndex(), array);
//处理一下数组中的数据
handleArray(array, 0, array.length);

Netty使用CompositeByteBuf来优化socket的IO操作,避免了JDK缓冲区实现所导致的性能和内存使用率的缺陷.内存使用率的缺陷是指对可复用对象大量的复制,Netty对其在内部做了优化,虽然没有暴露出来,但是应该知道CompositeByteBuf的优势和JDK自带工具的弊端.

JDK的NIO包中提供了Scatter/Gather I/O技术,字面意思是打散和聚合,可以理解为把单个ByteBuffer切分成多个或者把多个ByteBuffer合并成一个.

3.字节级操作

ByteBuf的索引从0开始,最后一个索引是capacity()-1.

遍历演示

ByteBuf buffer = Unpooled.buffer(1024);
for (int i = 0; i < buffer.capacity(); i++) {
byte b = buffer.getByte(i);//这种方法不会移动readerIndex指针
System.out.println((char) b);
}

3.1readerIndex和writerIndex

JDK中的ByteBuffer只有一个索引,需要通过flip()来切换读写操作,Netty中的ByteBuf既有读索引,也有写索引,通过两个索引把ByteBuf划分了三部分.

可以调用discardReadBytes() 方法可丢弃可丢弃字节并回收空间.

调用discardReadBytes() 方法之后

使用read*skip*方法都会增加readerIndex.

移动readerIndex读取可读数据的方式

ByteBuf buffer = ...;
while (buffer.isReadable()) {
System.out.println(buffer.readByte());
}

write*方法写入ByteBuf时会增加writerIndex,如果超过容量会抛出IndexOutOfBoundException .

writeableBytes()可以返回可写字节数.

ByteBuf buffer = ...;
while (buffer.writableBytes() >= 4) {
buffer.writeInt(random.nextInt());
}

3.2索引管理

JDK 的 InputStream 定义了 mark(int readlimit)reset()方法,这些方法分别被用来将流中的当前位置标记为指定的值,以及将流重置到该位置。

同样,可以通过调用 markReaderIndex() markWriterIndex()resetWriterIndex() resetReaderIndex()来标记和重置 ByteBuf readerIndexwriterIndex。这些和InputStream 上的调用类似,只是没有 readlimit 参数来指定标记什么时候失效。

如果将索引设置到一个无效位置会抛出IndexOutOfBoundsException.

可以通过clear()归零索引,归零索引不会清除数据.

3.3查找

ByteBuf中很多方法可以确定的索引,如indexOf().

复杂查找可以通过那些需要一个ByteBufProcessor作为参数的方法完成.这个接口应该可以使用lambda表达式(但是我现在使用的Netty4.1.12已经废弃了该接口,应该使用ByteProcessor).

ByteBuf buffer = ...;
int index = buffer.forEachByte(ByteProcessor.FIND_CR);

3.4派生缓冲区

派生缓冲区就是,基于原缓冲区一顿操作生成新缓冲区.比如复制,切分等等.

duplicate()slice()slice(int, int) ;Unpooled.unmodifiableBuffer(…) ;order(ByteOrder)readSlice(int) .

每个这些方法都将返回一个新的 ByteBuf 实例,它具有自己的读索引、写索引和标记

索引。 其内部存储和 JDK 的 ByteBuffer 一样也是共享的。这使得派生缓冲区的创建成本

是很低廉的,但是这也意味着,如果你修改了它的内容,也同时修改了其对应的源实例,所

以要小心

//复制
public static void byteBufCopy() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf copy = buf.copy(0, 15);
System.out.println(copy.toString(utf8));
buf.setByte(0, (byte)'J');
assert buf.getByte(0) != copy.getByte(0);
}
//切片
public static void byteBufSlice() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf sliced = buf.slice(0, 15);
System.out.println(sliced.toString(utf8));
buf.setByte(0, (byte)'J');
assert buf.getByte(0) == sliced.getByte(0);
}

还有一些读写操作的API,留在文末展示吧.

4.ByteBufHolder接口

我们经常发现, 除了实际的数据负载之外, 我们还需要存储各种属性值。 HTTP 响应便是一个很好的例子, 除了表示为字节的内容,还包括状态码、 cookie 等。

为了处理这种常见的用例, Netty 提供了 ByteBufHolder。 ByteBufHolder 也为 Netty 的高级特性提供了支持,如缓冲区池化,其中可以从池中借用 ByteBuf, 并且在需要时自动释放。ByteBufHolder 只有几种用于访问底层数据和引用计数的方法。

5.ByteBuf的分配

我们可以通过ByteBufAllocator来分配一个ByteBuf实例.ByteBufAllocator接口实现了ByteBuf的池化.

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

//从Channel获取一个ByteBufAllocator的引用
Channel channel = ...;
ByteBufAllocator allocator = channel.alloc();
....
//从ChannelHandlerContext获取ByteBufAllocator 的引用
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc();

Netty提供了两种ByteBufAllocator的实现: PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。 后者的实现不 池化ByteBuf实例, 并且在每次它被调用时都会返回一个新的实例。

默认使用的是PooledByteBufAllocator ,可以通过ChannelConfig修改.

Unpooled缓冲区

可能有时候拿不到ByteBufAllocator引用的话,可以使用Unpooled工具类来创建未持化ByteBuf实例.

ByteBufUtil类

ByteBufUtil 提供了用于操作 ByteBuf 的静态的辅助方法。因为这个 API 是通用的, 并且和池化无关,所以这些方法已然在分配类的外部实现。

这些静态方法中最有价值的可能就是 hexdump()方法, 它以十六进制的表示形式打印ByteBuf 的内容。这在各种情况下都很有用,例如, 出于调试的目的记录 ByteBuf 的内容。十六进制的表示通常会提供一个比字节值的直接表示形式更加有用的日志条目,此外,十六进制的版本还可以很容易地转换回实际的字节表示。

另一个有用的方法是 boolean equals(ByteBuf, ByteBuf), 它被用来判断两个 ByteBuf实例的相等性。如果你实现自己的 ByteBuf 子类,你可能会发现 ByteBufUtil 的其他有用方法。

6.引用计数

引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。 它们都实现了 interface ReferenceCounted。 引用计数背后的想法并不是特别的复杂;它主要涉及跟踪到某个特定对象的活动引用的数量。一个 ReferenceCounted 实现的实例将通常以活动的引用计数为 1 作为开始。只要引用计数大于 0, 就能保证对象不会被释放。当活动引用的数量减少到 0 时,该实例就会被释放。注意,虽然释放的确切语义可能是特定于实现的,但是至少已经释放的对象应该不可再用了。

//从Channel获取ByteBufAllocator
Channel channel = ...;
ByteBufAllocator allocator = channel.alloc();
....
//从ByteBufAllocator分配一个ByteBuf
ByteBuf buffer = allocator.directBuffer();
assert buffer.refCnt() == 1;//引用计数是否为1

7.API

ByteBuf

ByteBufAllocator

Unpooled

Netty之ByteBuf的更多相关文章

  1. 【Netty】Netty之ByteBuf

    一.前言 前面已经学习了Netty中传输部分,现在接着学习Netty中的ByteBuf. 二.ByteBuf 2.1 ByteBuf API 在网络上传输的数据形式为Byte,Java NIO提供了B ...

  2. Netty中ByteBuf的引用计数线程安全的实现原理

    原文链接 Netty中ByteBuf的引用计数线程安全的实现原理 代码仓库地址 ByteBuf 实现了ReferenceCounted 接口,实现了引用计数接口,该接口的retain(int) 方法为 ...

  3. Netty的ByteBuf

    https://blog.csdn.net/thinking_fioa/article/details/80795673  netty的ByteBuf知识点

  4. java.neo的ByteBuffer与Netty 的ByteBuf

    JDK的ByteBuffer的缺点: 1.final byte[] hb;这是JDKde ByteBuffer对象中用于存储数据的对象声明;可以看到,其字节数组是被声明为final的,也就是长度是固定 ...

  5. Netty笔记--ByteBuf释放

    参考资料:http://www.maljob.com/pages/newsDetail.html?id=394 参考资料:http://www.blogjava.net/liuguly/archive ...

  6. Netty中ByteBuf 的零拷贝

    转载:https://www.jianshu.com/p/1d1fa2fe1ed9 此文章已同步发布在我的 segmentfault 专栏. 根据 Wiki 对 Zero-copy 的定义: &quo ...

  7. Netty Associated -- ByteBuf

    ByteBuf ByteBuf是Netty的Server与Client之间通信的数据传输载体.他提供了一个byte数组(byte[])的抽象视图 buffer创建 我们推荐通过一个Unpooled的帮 ...

  8. netty LEAK: ByteBuf.release() was not called before it's garbage-collected

    背景.netty抛出完整的error信息如下: 2018-02-08 14:30:43.098 [nioEventLoopGroup-5-1] ERROR io.netty.util.Resource ...

  9. netty之ByteBuf详解

    [ChannelPromise作用:可以设置success或failure 是为了通知ChannelFutureListener]Netty的数据处理API通过两个组件暴露——abstract cla ...

随机推荐

  1. 矩阵中的路径 牛客网 剑指Offer

    矩阵中的路径 牛客网 剑指Offer 题目描述 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下 ...

  2. cf 11D A Simple Task(状压DP)

    题意: N个点构成的无向图,M条边描述这个无向图. 问这个无向图中共有多少个环. (1 ≤ n ≤ 19, 0 ≤ m) 思路: 例子: 4 6 1 2 1 3 1 4 2 3 2 4 3 4 答案: ...

  3. 如何抓取直播源及视频URL地址-疯狂URL(教程)

    直播源介绍 首先,我们来快速了解一下什么是直播源,所谓的直播源,其实就说推流地址,推流地址可能你也不知道是什么,那么我再简单说一下,推流地址就是,当某个直播开播的时候,需要将自己的直播状态实时的展示给 ...

  4. oracle 数据库修改端口号1521

    1.关闭监听 2.修改配置文件,port=1933 #vi $ORACLE_HOME/network/admin/listener.ora 3.登录并查看local_listener参数,因为使用的是 ...

  5. 转向系统的传递路径分析(Transfer Path Analysis)入门的一些分享

    分享一些自己对于<转向系统><传递路径分析>的理解 (只是一些个人理解,不涉及任何公司隐私问题,logo就懒得一个个去擦了) (1) (2) (3) (4) (5) (6) ( ...

  6. PTA 7-3 Windows消息队列 (25分)

    PTA 7-3 Windows消息队列 (25分) 消息队列是Windows系统的基础.对于每个进程,系统维护一个消息队列.如果在进程中有特定事件发生,如点击鼠标.文字改变等,系统将把这个消息加到队列 ...

  7. JVM启动参数详解

    JVM启动参数以及具体的解释: -Xmx1024M 最大堆内存 -Xms1024M 初始化堆内存,正常和最大堆内存相同,减少动态改变的内存损耗 -Xmn384M 年轻代内存 -XX:PermSize= ...

  8. Unity——技能系统(三)

    Unity技能系统(三) Unity技能系统(一) Unity技能系统(二) Demo展示 六.Buff系统 buff分为增益和减益buff,应该区分开来: /// <summary> / ...

  9. 湖湘杯2020 writeup

    这个平台中间卡的离谱,卡完过后交了flag分还掉了 Web 题目名字不重要 也算是非预期吧,赛后y1ng师傅也说了因为要多端口环境必须这样配,预期解很难 NewWebsite 后台弱口令admin a ...

  10. 史上最简单的排序算法?看起来却满是bug

    大家好,我是雨乐. 今天在搜论文的时候,偶然发现一篇文章,名为<Is this the simplest (and most surprising) sorting algorithm ever ...