netty案例,netty4.1源码分析篇四《ByteBuf的数据结构在使用方式中的剖析》

简介: 在Netty中ByteBuf是一个非常重要的类,它可以以高效易用的数据结构方式来满足网络通信过程中处理数据包内字节码序列的移动。

24.jpg

前言介绍

在Netty中ByteBuf是一个非常重要的类,它可以以高效易用的数据结构方式来满足网络通信过程中处理数据包内字节码序列的移动。

数据结构

+-------------------+------------------+------------------+
 | discardable bytes |  readable bytes  |  writable bytes  |
 |                   |     (CONTENT)    |                  |
 +-------------------+------------------+------------------+
 |                   |                  |                  |
 0      <=      readerIndex   <=   writerIndex    <=    capacity

那么这种数据结构之所以能高效的处理数据传输处理并解决半包粘包,主要得益于ByteBuf中有两个不同的索引;读索引(readIndex)、写索引(writerIndex)。

读索引:当我们从ByteBuf读取数据时,readerIndex指针位置也会指向到读取字节位置

写索引:当我们向ByteBuf写入数据时,writerIndex指针位置也会指向到写入字节位置

discardable bytes:当从ByteBuf读取一部分数据后,这部分数据就属于discardable,他们是可以被废弃的。

readable bytes:剩余可以继续读取的内容区域,就像一个长条的鸡蛋卡槽,一边放一边拿。从已经拿完到剩余的鸡蛋位置属于可拿区域。

writable bytes:同上一样,这一部分就是可以继续放置鸡蛋的位置。

功能案例

// 62 75 67 73 74 61 63 6B B3 E6 B6 B4 D5 BB
public static void main(String[] args) throws UnsupportedEncodingException {
    // 1.创建一个非池化的ByteBuf,大小为14个字节
    ByteBuf buffer = Unpooled.buffer(14);
    System.out.println("1.创建一个非池化的ByteBuf,大小为14个字节");
    System.out.println("ByteBuf空间大小:" + buffer.capacity());
    // 2.写入3个字节
    buffer.writeByte(62);
    buffer.writeByte(75);
    buffer.writeByte(67);
    System.out.println("\r\n2.写入3个字节");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    // 3.写入一段字节
    byte[] bytes = {73, 74, 61, 63, 0x6B};
    buffer.writeBytes(bytes);
    System.out.println("\r\n3.写入一段字节");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    // 4.读取全部内容
    byte[] allBytes = new byte[buffer.readableBytes()];
    buffer.readBytes(allBytes);
    System.out.println("\r\n4.读取全部内容");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    System.out.println("读取全部内容:" + Arrays.toString(allBytes));
    // 5.重置指针位置
    buffer.resetReaderIndex();
    System.out.println("\r\n5.重置指针位置");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    // 6.读取3个字节
    byte b0 = buffer.readByte();
    byte b1 = buffer.readByte();
    byte b2 = buffer.readByte();
    System.out.println("\r\n6.读取3个字节");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    System.out.println("读取3个字节:" + Arrays.toString(new byte[]{b0, b1, b2}));
    // 7.读取一段字节
    ByteBuf byteBuf = buffer.readBytes(5);
    byte[] dst = new byte[5];
    byteBuf.readBytes(dst);
    System.out.println("\r\n7.读取一段字节");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    System.out.println("读取一段字节:" + Arrays.toString(dst));
    // 8.丢弃已读内容
    buffer.discardReadBytes();
    System.out.println("\r\n8.丢弃已读内容");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    // 9.清空指针位置
    buffer.clear();
    System.out.println("\r\n9.清空指针位置");
    System.out.println("readerIndex位置:" + buffer.readerIndex());
    System.out.println("writerIndex位置:" + buffer.writerIndex());
    // 10.ByteBuf中还有很多其他方法;拷贝、标记、跳过字节,多用于自定义解码器进行半包粘包处理
}
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
    //基础长度不足,我们设定基础长度为4
    if (in.readableBytes() < BASE_LENGTH) {
        return;
    }
    int beginIdx; //记录包头位置
    while (true) {
        // 获取包头开始的index
        beginIdx = in.readerIndex();
        // 标记包头开始的index
        in.markReaderIndex();
        // 读到了协议的开始标志,结束while循环
        if (in.readByte() == 0x02) {
            break;
        }
        // 未读到包头,略过一个字节
        // 每次略过,一个字节,去读取,包头信息的开始标记
        in.resetReaderIndex();
        in.readByte();
        // 当略过,一个字节之后,
        // 数据包的长度,又变得不满足
        // 此时,应该结束。等待后面的数据到达
        if (in.readableBytes() < BASE_LENGTH) {
            return;
        }
    }
    //剩余长度不足可读取数量[没有内容长度位]
    int readableCount = in.readableBytes();
    if (readableCount <= 1) {
        in.readerIndex(beginIdx);
        return;
    }
    //长度域占4字节,读取int
    ByteBuf byteBuf = in.readBytes(1);
    String msgLengthStr = byteBuf.toString(Charset.forName("GBK"));
    int msgLength = Integer.parseInt(msgLengthStr);
    //剩余长度不足可读取数量[没有结尾标识]
    readableCount = in.readableBytes();
    if (readableCount < msgLength + 1) {
        in.readerIndex(beginIdx);
        return;
    }
    ByteBuf msgContent = in.readBytes(msgLength);
    //如果没有结尾标识,还原指针位置[其他标识结尾]
    byte end = in.readByte();
    if (end != 0x03) {
        in.readerIndex(beginIdx);
        return;
    }
    out.add(msgContent.toString(Charset.forName("GBK")));
}

内存模型

1、堆内内存(JVM堆空间内)

最常用的ByteBuf模式是将数据存储在JVM的堆空间中。它能在没有使用池化的情况下提供快速的分配和释放。

2、堆外内存(本机直接内存)

JDK允许JVM实现通过本地调用来分配内存。主要是为了避免每次调用本地I/O操作之前(或者之后)将缓冲区的内容复制到一个中间缓冲区(或者从中间缓冲区把内容复制到缓冲区)。

3、复合缓冲区(以上2种缓冲区多个混合)

常用类:CompositeByteBuf,它为多个ByteBuf提供一个聚合视图,将多个缓冲区表示为单个合并缓冲区的虚拟表示。

比如:HTTP协议:头部和主体这两部分由应用程序的不同模块产生。这个时候把这两部分合并的话,选择CompositeByteBuf是比较好的。

源码解读

ByteBuf实现了ReferenceCounted与Comparable两个接口

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf>

创建一个指定容量大小堆缓冲区,并按需扩充容量{与list集和很像},指针位置都是从0开始

Unpooled.buffer(14);
/**
 * Creates a new big-endian Java heap buffer with the specified {@code capacity}, which
 * expands its capacity boundlessly on demand.  The new buffer's {@code readerIndex} and
 * {@code writerIndex} are {@code 0}.
 */
public static ByteBuf buffer(int initialCapacity) {
    return ALLOC.heapBuffer(initialCapacity);
}

跟进代码的创建过程会发现,UnpooledHeapByteBuf.UnpooledHeapByteBuf用来创建非池化堆Buf

**
 * Creates a new heap buffer with an existing byte array.
 *
 * @param initialArray the initial underlying byte array
 * @param maxCapacity the max capacity of the underlying byte array
 */
protected UnpooledHeapByteBuf(ByteBufAllocator alloc, byte[] initialArray, int maxCapacity) {
    super(maxCapacity);
    checkNotNull(alloc, "alloc");
    checkNotNull(initialArray, "initialArray");
    if (initialArray.length > maxCapacity) {
        throw new IllegalArgumentException(String.format(
                "initialCapacity(%d) > maxCapacity(%d)", initialArray.length, maxCapacity));
    }
    this.alloc = alloc;
    setArray(initialArray);
    setIndex(0, initialArray.length);
}

内容总结

  • ByteBuf提供了两个指针;读、写,分别用来标记“可读”、“可写”、“可丢弃”的字节
  • 通过调用write*方法写入数据后,写指针将会向后移动
  • 通过调用read*方法读取数据后,读指针将会向后移动
  • 写入数据或读取数据时会检查是否有足够多的空间可以写入和是否有数据可以读取
  • 写入数据之前会进行容量检查,当剩余可写的容量小于需要写入的容量时,需要执行扩容操作
  • clear等修改读写指针的方法,只会更改读写指针的值,并不会影响ByteBuf中已有的内容
目录
相关文章
|
7月前
|
Java API 容器
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
139 0
|
4月前
|
网络协议 大数据 Linux
Netty的源码分析和业务场景
通过深入分析 Netty 的源码和理解其在不同业务场景下的应用,开发者可以更好地利用这一强大的网络编程框架,构建高效、稳定且可扩展的网络应用。
230 1
|
4月前
|
传感器 物联网 微服务
Netty的源码分析和业务场景
【8月更文挑战第2天】Netty 是一款高性能的异步事件驱动网络框架,其源码深邃且复杂。通过采用Reactor模式与主从多线程设计,Netty能高效处理网络事件。例如,`NioEventLoop`负责I/O事件及任务执行,内置线程循环机制。内存管理方面,Netty提供高效内存池与`ByteBuf`类来减少开销并优化内存操作。在业务场景上,Netty广泛应用于分布式系统、微服务架构中的高效通信,以及实时通信场景如在线游戏和直播中的大量并发连接处理,同时也在物联网领域发挥重要作用,确保设备与服务器间稳定快速的数据传输。
|
5月前
|
消息中间件 存储 NoSQL
Redis数据结构—跳跃表 skiplist 实现源码分析
Redis 是一个内存中的数据结构服务器,使用跳跃表(skiplist)来实现有序集合。跳跃表是一种概率型数据结构,支持平均 O(logN) 查找复杂度,它通过多层链表加速查找,同时保持有序性。节点高度随机生成,最大为 32 层,以平衡查找速度和空间效率。跳跃表在 Redis 中用于插入、删除和按范围查询元素,其内部节点包含对象、分值、后退指针和多个前向指针。Redis 源码中的 `t_zset.c` 文件包含了跳跃表的具体实现细节。
|
6月前
netty查看ByteBuf工具
netty查看ByteBuf工具
|
7月前
|
存储 API
milvus insert api的数据结构源码分析
milvus insert api的数据结构源码分析
1065 6
milvus insert api的数据结构源码分析
|
7月前
|
Java API 索引
Netty Review - ByteBuf 读写索引 详解
Netty Review - ByteBuf 读写索引 详解
200 1
|
7月前
|
API 容器
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf(一)
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
79 0
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf(一)
|
7月前
|
前端开发 网络协议 Java
Netty | 工作流程图分析 & 核心组件说明 & 代码案例实践
Netty | 工作流程图分析 & 核心组件说明 & 代码案例实践
409 0
|
7月前
|
监控 网络协议 调度
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
452 0