Netty ByteBuf 傳輸載體
1. 前言
在 Netty 里面的數(shù)據(jù)讀寫是以 ByteBuf 為單位進行交互的,ByteBuf 是一個字節(jié)容器,如果了解過 NIO 的同學應該知道,在 NIO 里面也有類型的數(shù)據(jù)載體 ByteBuffer。
2. 學習目的
熟悉掌握 ByteBuf 的原理及 API,則可以自定義通信協(xié)議,并且使用 ByteBuf、序列化等技術實現(xiàn)通信協(xié)議,并且有效解決拆包和粘包問題(后面章節(jié)會詳細分析)。
3. ByteBuf 結構
ByteBuff 的結構主要由四個部分組成,廢棄字節(jié)、可讀字節(jié)、可寫字節(jié)、可擴容字節(jié),具體結構圖如下所示:
通過以上的結構圖,我們可以看出它其實就是一個數(shù)組,在寫數(shù)據(jù)和讀數(shù)據(jù)的時候分別維護兩個指針的移動,分別是 readerIndex 和 writerIndex,在 readerIndex 和 writerIndex 之間的數(shù)據(jù)是有效可讀數(shù)據(jù)。具體分析如下所示:
- ByteBuf 的四個核心屬性,分別是 readerIndex(可讀指針位置)、writerIndex(可寫指針位置)、capacity(初始化容量值)、maxCapacity(最大容量值)。其中 readerIndex 和 writerIndex 之間的數(shù)據(jù)是 ByteBuf 的主體數(shù)據(jù);
- 讀取數(shù)據(jù)的時候,readerIndex 遞增,一旦 readerIndex 等于 writerIndex 則表示該容器沒有數(shù)據(jù)可讀了。writerIndex-readerIndex 表示有效可讀數(shù)據(jù)的長度;
- 寫數(shù)據(jù)的時候,writerIndex 遞增,一旦 writerIndex 等于 capacity 表示容器已經(jīng)滿了,ByteBuf 不能再寫數(shù)據(jù)了,capacity-writerIndex 表示容器還可以寫入的數(shù)據(jù)長度;
- 當向 ByteBuf 寫數(shù)據(jù)的時候,如果容量不足,那么這個時候可以進行擴容,直到 capacity 擴容到 maxCapacity,超過 maxCapacity 就會報錯;
- 總結,readerIndex<=writerIndex<=capacity<=maxCapacity。
4. 核心 API
方法 | 描述 |
---|---|
capacity() | 容量 |
maxCapacity() | 最大容量(當容量最大時,還可以擴容) |
readableBytes() | 可讀字節(jié)數(shù) |
isReadable() | 是否可讀 |
writableBytes() | 可寫字節(jié)數(shù) |
isWritable() | 是否可寫 |
maxWritableBytes() | 最大可寫字節(jié)數(shù) |
readerIndex() | 讀指針 |
readerIndex(int) | 重置讀指針為某個位置 |
writerIndex() | 寫指針 |
writeIndex(int) | 重置寫指針為某個位置 |
markReaderIndex() | 保存當前讀指針 |
resetReaderIndex() | 回歸之前保存的讀指針 |
markWriterIndex() | 保存當前寫指針 |
resetWriterIndex | 回歸之前保存的寫指針 |
writeByte(int i) | 寫一個字節(jié) |
writeBytes(byte[] bytes) | 寫一個字節(jié)數(shù)組 |
readByte() | 讀一個字節(jié) |
readByte(byte[] bytes) | 讀一個字節(jié)數(shù)組(并往參數(shù) bytes 里存放) |
以上是 ByteBuf 的核心的 API,很多時候,在編程的時候直接操作 ByteBuf 可能會相對的繁瑣,所以不會直接手工調用這些 API,而是通過封裝編碼和解碼器的方式進行使用,但是編碼、解碼底層就是通過 ByteBuf 去實現(xiàn)的。
5. 核心 API 詳解
5.1 capatiy()
表示 ByteBuf 可以寫入多少個字節(jié),一般在初始化 ByteBuf 時就會指定。
實例:
ByteBuf byteBuf=Unpooled.buffer(10);
5.2 maxCapacity()
表示 ByteBuf 最大可以支持多少字節(jié),如果當 writerIndex=capacity 時,會判斷 capacity 是否等于 maxCapacity,如果小于則擴容。
實例:
//參數(shù)1,容量值
//參數(shù)2,最大容量值
ByteBuf byteBuf = Unpooled.buffer(10,20);
5.3 readalbeBytes()
表示 ByteBuf 當前可讀的字節(jié)數(shù),它的值等于 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 當前可寫的字節(jié)數(shù),它的值等于 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 相等,并不代表不能繼續(xù)往 ByteBuf 寫數(shù)據(jù)了。如果發(fā)現(xiàn)往 ByteBuf 中寫數(shù)據(jù)寫不進去的話,Netty 會自動擴容 ByteBuf,直到擴容到底層的內存大小為 maxCapacity,而 maxWritableBytes () 就表示可寫的最大字節(jié)數(shù),它的值等于 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 () 表示把當前的讀指針保存起來,其實類似數(shù)據(jù)庫事務的當前狀態(tài)標記。
源碼:
public ByteBuf markReaderIndex() {
//使用markedReaderIndex保存當前讀指針
this.markedReaderIndex = this.readerIndex;
return this;
}
5.13 resetReaderIndex()
resetReaderIndex () 表示把當前的讀指針恢復到之前保存的值,類似數(shù)據(jù)庫事務回歸到某個狀態(tài)。
源碼:
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),表示一次寫入一個字節(jié),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),表示寫入一個字節(jié)數(shù)組,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 (),表示一次讀取一個字節(jié),readerIndex++。
源碼:
public byte readByte() {
this.checkReadableBytes0(1);
int i = this.readerIndex;
byte b = this._getByte(i);//讀取數(shù)據(jù)
this.readerIndex = i + 1; //指針自增
return b;
}
5.19 readBytes(byte[] dst)
readBytes (byte [] dst),表示把 ByteBuf 里面的數(shù)據(jù)讀取到 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里面寫數(shù)據(jù)
this.readerIndex += length;//指針增加
return this;
}
讀寫 API 類似的 API 還有 getBytes、getByte () 與 setBytes ()、setByte () 系列
區(qū)別就是 get/set 不會改變讀寫指針,而 read/write 會改變讀寫指針,這點在解析數(shù)據(jù)的時候千萬要注意。
6. 小結
本節(jié)內容主要講解了 ByteBuf,需要掌握的內容如下:
- 理解并且牢記 ByteBuf 結構圖;
- 基于讀寫指針、容量、最大可擴容容量,衍生出一系列的讀寫方法,要注意 read/write 與 get/set 的區(qū)別;