章节索引 :

Netty ByteBuf 传输载体

1. 前言

在 Netty 里面的数据读写是以 ByteBuf 为单位进行交互的,ByteBuf 是一个字节容器,如果了解过 NIO 的同学应该知道,在 NIO 里面也有类型的数据载体 ByteBuffer。

2. 学习目的

熟悉掌握 ByteBuf 的原理及 API,则可以自定义通信协议,并且使用 ByteBuf、序列化等技术实现通信协议,并且有效解决拆包和粘包问题(后面章节会详细分析)。

3. ByteBuf 结构

ByteBuff 的结构主要由四个部分组成,废弃字节、可读字节、可写字节、可扩容字节,具体结构图如下所示:

图片描述

通过以上的结构图,我们可以看出它其实就是一个数组,在写数据和读数据的时候分别维护两个指针的移动,分别是 readerIndex 和 writerIndex,在 readerIndex 和 writerIndex 之间的数据是有效可读数据。具体分析如下所示:

  1. ByteBuf 的四个核心属性,分别是 readerIndex(可读指针位置)、writerIndex(可写指针位置)、capacity(初始化容量值)、maxCapacity(最大容量值)。其中 readerIndex 和 writerIndex 之间的数据是 ByteBuf 的主体数据;
  2. 读取数据的时候,readerIndex 递增,一旦 readerIndex 等于 writerIndex 则表示该容器没有数据可读了。writerIndex-readerIndex 表示有效可读数据的长度;
  3. 写数据的时候,writerIndex 递增,一旦 writerIndex 等于 capacity 表示容器已经满了,ByteBuf 不能再写数据了,capacity-writerIndex 表示容器还可以写入的数据长度;
  4. 当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错;
  5. 总结,readerIndex<=writerIndex<=capacity<=maxCapacity。

4. 核心 API

方法 描述
capacity() 容量
maxCapacity() 最大容量(当容量最大时,还可以扩容)
readableBytes() 可读字节数
isReadable() 是否可读
writableBytes() 可写字节数
isWritable() 是否可写
maxWritableBytes() 最大可写字节数
readerIndex() 读指针
readerIndex(int) 重置读指针为某个位置
writerIndex() 写指针
writeIndex(int) 重置写指针为某个位置
markReaderIndex() 保存当前读指针
resetReaderIndex() 回归之前保存的读指针
markWriterIndex() 保存当前写指针
resetWriterIndex 回归之前保存的写指针
writeByte(int i) 写一个字节
writeBytes(byte[] bytes) 写一个字节数组
readByte() 读一个字节
readByte(byte[] bytes) 读一个字节数组(并往参数 bytes 里存放)

以上是 ByteBuf 的核心的 API,很多时候,在编程的时候直接操作 ByteBuf 可能会相对的繁琐,所以不会直接手工调用这些 API,而是通过封装编码和解码器的方式进行使用,但是编码、解码底层就是通过 ByteBuf 去实现的。

5. 核心 API 详解

5.1 capatiy()

表示 ByteBuf 可以写入多少个字节,一般在初始化 ByteBuf 时就会指定。

实例:

ByteBuf byteBuf=Unpooled.buffer(10);

5.2 maxCapacity()

表示 ByteBuf 最大可以支持多少字节,如果当 writerIndex=capacity 时,会判断 capacity 是否等于 maxCapacity,如果小于则扩容。

实例:

//参数1,容量值
//参数2,最大容量值
ByteBuf byteBuf = Unpooled.buffer(10,20);

5.3 readalbeBytes()

表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex。

源码:

public int readableBytes() {
    return this.writerIndex - this.readerIndex;
}

5.4 isReadable()

如果 writerIndex 和 readerIndex 相等,则不可读,isReadable () 方法返回 false。

源码:

public boolean isReadable(int numBytes) {
    return this.writerIndex - this.readerIndex >= numBytes;
}

5.5 writableBytes()

表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex。

源码:

public int writableBytes() {
    return this.capacity() - this.writerIndex;
}

5.6 isWritable()

如果 capacity 和 writerIndex 相等,则表示不可写,isWritable () 返回 false。

源码:

public boolean isWritable() {
    return this.capacity() > this.writerIndex;
}

5.7 maxWritableBytes()

capacity 和 writerIndex 相等,并不代表不能继续往 ByteBuf 写数据了。如果发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity,而 maxWritableBytes () 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex。

源码:

public int maxWritableBytes() {
    return this.maxCapacity() - this.writerIndex;
}

5.8 readerIndex()

readerIndex () 表示返回当前的读指针 readerIndex,ByteBuf 会维护一个变量 readerIndex。

源码:

int readerIndex;
public int readerIndex() {
    return this.readerIndex;
}

5.9 readerIndex(int)

readerIndex (int) 表示设置读指针,比如说可以回滚到某个指针位置。

源码:

public ByteBuf readerIndex(int readerIndex) {
        if (readerIndex >= 0 && readerIndex <= this.writerIndex) {
            //给readerIndex赋值
            this.readerIndex = readerIndex;
            return this;
        } else {
            throw new IndexOutOfBoundsException("....");
        }
    }

5.10 writeIndex()

writeIndex () 表示返回当前的写指针 writerIndex。

源码:

public int writerIndex() {
    return this.writerIndex;
}

5.11 writeIndex(int)

writeIndex (int) 表示设置写指针,比如说可以回滚到某个指针位置。

public ByteBuf writerIndex(int writerIndex) {
        if (writerIndex >= this.readerIndex && writerIndex <= this.capacity()) {
            //给writerIndex赋值
            this.writerIndex = writerIndex;
            return this;
        } else {
            throw new IndexOutOfBoundsException("...");
        }
    }

5.12 markReaderIndex()

markReaderIndex () 表示把当前的读指针保存起来,其实类似数据库事务的当前状态标记。

源码:

public ByteBuf markReaderIndex() {
    //使用markedReaderIndex保存当前读指针
    this.markedReaderIndex = this.readerIndex;
    return this;
}

5.13 resetReaderIndex()

resetReaderIndex () 表示把当前的读指针恢复到之前保存的值,类似数据库事务回归到某个状态。

源码:

public ByteBuf resetReaderIndex() {
    this.readerIndex(this.markedReaderIndex);
    return this;
}

5.14 markWriterIndex()

表示把当前的写指针保存起来。

源码:

public ByteBuf markWriterIndex() {
    this.markedWriterIndex = this.writerIndex;
    return this;
}

5.15 resetWriterIndex()

切块之前保存的写指针 writerIndex。

源码:

public ByteBuf resetWriterIndex() {
    this.writerIndex = this.markedWriterIndex;
    return this;
}

5.16 writeByte(byte b)

writeByte (byte b),表示一次写入一个字节,writerIndex++。

源码:

public ByteBuf writeByte(int value) {
    this.ensureAccessible();
    this.ensureWritable0(1);
    
    //writerIndex指针自增
    this._setByte(this.writerIndex++, value);
    return this;
}

5.17 writeBytes(byte[] src)

writeBytes (byte [] src),表示写入一个字节数组,writerIndex=writerIndex+src.length。

源码:

public ByteBuf writeBytes(byte[] src) {
    this.writeBytes((byte[])src, 0, src.length);
    return this;
}

public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
    this.ensureAccessible();
    this.ensureWritable(length);
    this.setBytes(this.writerIndex, src, srcIndex, length);
    this.writerIndex += length; //writerIndex指针增加
    return this;
}

5.18 readByte()

readByte (),表示一次读取一个字节,readerIndex++。

源码:

public byte readByte() {
    this.checkReadableBytes0(1);
    int i = this.readerIndex;
    byte b = this._getByte(i);//读取数据
    this.readerIndex = i + 1; //指针自增
    return b;
}

5.19 readBytes(byte[] dst)

readBytes (byte [] dst),表示把 ByteBuf 里面的数据读取到 dst,readerIndex=readerIndex+dst.length。

源码:

public ByteBuf readBytes(byte[] dst) {
    this.readBytes((byte[])dst, 0, dst.length);
    return this;
}

public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
    this.checkReadableBytes(length);
    this.getBytes(this.readerIndex, dst, dstIndex, length);//往目标dst里面写数据
    this.readerIndex += length;//指针增加
    return this;
}

读写 API 类似的 API 还有 getBytes、getByte () 与 setBytes ()、setByte () 系列

区别就是 get/set 不会改变读写指针,而 read/write 会改变读写指针,这点在解析数据的时候千万要注意。

6. 小结

本节内容主要讲解了 ByteBuf,需要掌握的内容如下:

  1. 理解并且牢记 ByteBuf 结构图;
  2. 基于读写指针、容量、最大可扩容容量,衍生出一系列的读写方法,要注意 read/write 与 get/set 的区别;