Netty ByteBuf 传输载体
1. 前言
在 Netty 里面的数据读写是以 ByteBuf 为单位进行交互的,ByteBuf 是一个字节容器,如果了解过 NIO 的同学应该知道,在 NIO 里面也有类型的数据载体 ByteBuffer。
2. 学习目的
熟悉掌握 ByteBuf 的原理及 API,则可以自定义通信协议,并且使用 ByteBuf、序列化等技术实现通信协议,并且有效解决拆包和粘包问题(后面章节会详细分析)。
3. ByteBuf 结构
ByteBuff 的结构主要由四个部分组成,废弃字节、可读字节、可写字节、可扩容字节,具体结构图如下所示:
通过以上的结构图,我们可以看出它其实就是一个数组,在写数据和读数据的时候分别维护两个指针的移动,分别是 readerIndex 和 writerIndex,在 readerIndex 和 writerIndex 之间的数据是有效可读数据。具体分析如下所示:
- ByteBuf 的四个核心属性,分别是 readerIndex(可读指针位置)、writerIndex(可写指针位置)、capacity(初始化容量值)、maxCapacity(最大容量值)。其中 readerIndex 和 writerIndex 之间的数据是 ByteBuf 的主体数据;
- 读取数据的时候,readerIndex 递增,一旦 readerIndex 等于 writerIndex 则表示该容器没有数据可读了。writerIndex-readerIndex 表示有效可读数据的长度;
- 写数据的时候,writerIndex 递增,一旦 writerIndex 等于 capacity 表示容器已经满了,ByteBuf 不能再写数据了,capacity-writerIndex 表示容器还可以写入的数据长度;
- 当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错;
- 总结,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,需要掌握的内容如下:
- 理解并且牢记 ByteBuf 结构图;
- 基于读写指针、容量、最大可扩容容量,衍生出一系列的读写方法,要注意 read/write 与 get/set 的区别;