Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍
##
Netty實戰 IM即時通訊系統(七)數據傳輸載體ByteBuf介紹零、 目錄
- Netty 簡介
- Netty 環境配置
- 服務端啟動流程
- 客戶端啟動流程
- 實戰: 客戶端和服務端雙向通信
- 數據傳輸載體ByteBuf介紹
- 客戶端與服務端通信協議編解碼
- 實現客戶端登錄
- 實現客戶端與服務端收發消息
- pipeline與channelHandler
- 構建客戶端與服務端pipeline
- 拆包粘包理論與解決方案
- channelHandler的生命周期
- 使用channelHandler的熱插拔實現客戶端身份校驗
- 客戶端互聊原理與實現
- 群聊的發起與通知
- 群聊的成員管理(加入與退出,獲取成員列表)
- 群聊消息的收發及Netty性能優化
- 心跳與空閑檢測
- 總結
- 擴展
七、 數據傳輸載體ByteBuf 介紹
前面的小節中我們了解到Netty的數據讀寫都是以ByteBuf 為單位進行交互的 , 我們接下來就剖析一下ByteBuf
ByteBuf結構
Api
readerIndex() 和 readerIndex(int): 前者表示獲取當前讀指針位置 , 后者表示設置讀指針位置
writeIndex() 與 writeIndex(int): 前者表示獲取當前寫指針位置 , 后者表示設置寫指針位置
markReaderIndex() 與 resetReaderIndex(): 前者表示把當前的讀指針保存起來 , 后者表示 把當前的讀指針恢復到之前保存的值
// 代碼片段1int readerIndex = buffer.readerIndex();// .. 其他操作buffer.readerIndex(readerIndex);// 代碼片段二buffer.markReaderIndex();// .. 其他操作buffer.resetReaderIndex();writeBytes(byte[] src) 與 buffer.readBytes(byte[] dst) :writeBytes(bs) 表示把字節數組bs中的字節全部寫到ByteBuf , 而readBytes()指的是把ByteBuf里的數據全部讀取到dst , 這里dst的字節數組的大小通常等于readableBytes() , 而src的長度通常小于writeableBytes();
writeByte(byte b) 與 readByte():writeByte()表示往ByteBuf中寫入一個字節 , 類似的API還有writeBoolean() , writeChar() , writeShort()、writeLong()、writeFloat()、writeDouble() 與 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() 這里就不一一贅述了,相信讀者應該很容易理解這些 API 。 與讀寫 API 類似的 API 還有 getBytes、getByte() 與 setBytes()、setByte() 系列,唯一的區別就是 get/set 不會改變讀寫指針,而 read/write 會改變讀寫指針,這點在解析數據的時候千萬要注意
release() 與 retain(): 由于 Netty 使用了堆外內存,而堆外內存是不被 jvm 直接管理的,也就是說申請到的內存無法被垃圾回收器直接回收,所以需要我們手動回收。有點類似于c語言里面,申請到的內存必須手工釋放,否則會造成內存泄漏。Netty 的 ByteBuf 是通過引用計數的方式管理的,如果一個 ByteBuf 沒有地方被引用到,需要回收底層內存。默認情況下,當創建完一個 ByteBuf,它的引用為1,然后每次調用 retain() 方法, 它的引用就加一, release() 方法原理是將引用計數減一,減完之后如果發現引用計數為0,則直接回收 ByteBuf 底層的內存。
slice()、duplicate()、copy():這三個方法通常情況會放到一起比較,這三者的返回值都是一個新的 ByteBuf 對象
retainedSlice() 與 retainedDuplicate():相信讀者應該已經猜到這兩個 API 的作用了,它們的作用是在截取內存片段的同時,增加內存的引用計數,分別與下面兩段代碼等價
// retainedSlice 等價于slice().retain();// retainedDuplicate() 等價于使用到 slice 和 duplicate 方法的時候,千萬要理清內存共享,引用計數共享,讀寫指針不共享幾個概念,下面舉兩個常見的易犯錯的例子
多次釋放Buffer buffer = xxx;doWith(buffer);// 一次釋放buffer.release();public void doWith(Bytebuf buffer) {// ... // 沒有增加引用計數Buffer slice = buffer.slice();foo(slice);}public void foo(ByteBuf buffer) {// read from buffer// 重復釋放buffer.release();}這里的 doWith 有的時候是用戶自定義的方法,有的時候是 Netty 的回調方法,比如 channelRead() 等等不釋放造成內存泄漏Buffer buffer = xxx;doWith(buffer);// 引用計數為2,調用 release 方法之后,引用計數為1,無法釋放內存 buffer.release();public void doWith(Bytebuf buffer) {// ... // 增加引用計數Buffer slice = buffer.retainedSlice();foo(slice);// 沒有調用 release}public void foo(ByteBuf buffer) {// read from buffer}實戰:
代碼
public class Test_08_ByteBuf介紹 {public static void main(String[] args) {ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 200);print("allocate ByteBuf(9, 100)", buffer);// write 方法改變寫指針,寫完之后寫指針未到 capacity 的時候,buffer 仍然可寫buffer.writeBytes(new byte[] { 1, 2, 3, 4 });print("writeBytes(1,2,3,4)", buffer);// write 方法改變寫指針,寫完之后寫指針未到 capacity 的時候,buffer 仍然可寫, 寫完 int 類型之后,寫指針增加4buffer.writeInt(10);print("writeInt(10)", buffer);// write 方法改變寫指針, 寫完之后寫指針等于 capacity 的時候,buffer 不可寫buffer.writeBytes(new byte[] { 5 });print("writeBytes(5)", buffer);// write 方法改變寫指針,寫的時候發現 buffer 不可寫則開始擴容,擴容之后 capacity 隨即改變buffer.writeBytes(new byte[] { 6 });print("writeBytes(6)", buffer);// get 方法不改變讀寫指針System.out.println("getByte(3) return: " + buffer.getByte(3));System.out.println("getShort(3) return: " + buffer.getShort(3));System.out.println("getInt(3) return: " + buffer.getInt(3));print("getByte()", buffer);// set 方法不改變讀寫指針buffer.setByte(buffer.readerIndex() + buffer.readableBytes() + 1, 0);print("setByte()", buffer);// read 方法改變讀指針byte[] dst = new byte[buffer.readableBytes()];buffer.readBytes(dst);print("readBytes(" + dst.length + ")", buffer);}private static void print(String action, ByteBuf buffer) {System.out.println("after ===========" + action + "============");System.out.println("capacity(): " + buffer.capacity());System.out.println("maxCapacity(): " + buffer.maxCapacity());System.out.println("readerIndex(): " + buffer.readerIndex());System.out.println("readableBytes(): " + buffer.readableBytes());System.out.println("isReadable(): " + buffer.isReadable());System.out.println("writerIndex(): " + buffer.writerIndex());System.out.println("writableBytes(): " + buffer.writableBytes());System.out.println("isWritable(): " + buffer.isWritable());System.out.println("maxWritableBytes(): " + buffer.maxWritableBytes());System.out.println();}}執行結果: after ===========allocate ByteBuf(9, 100)============capacity(): 9maxCapacity(): 200readerIndex(): 0readableBytes(): 0isReadable(): falsewriterIndex(): 0writableBytes(): 9isWritable(): truemaxWritableBytes(): 200after ===========writeBytes(1,2,3,4)============capacity(): 9maxCapacity(): 200readerIndex(): 0readableBytes(): 4isReadable(): truewriterIndex(): 4writableBytes(): 5isWritable(): truemaxWritableBytes(): 196after ===========writeInt(10)============capacity(): 9maxCapacity(): 200readerIndex(): 0readableBytes(): 8isReadable(): truewriterIndex(): 8writableBytes(): 1isWritable(): truemaxWritableBytes(): 192after ===========writeBytes(5)============capacity(): 9maxCapacity(): 200readerIndex(): 0readableBytes(): 9isReadable(): truewriterIndex(): 9writableBytes(): 0isWritable(): falsemaxWritableBytes(): 191after ===========writeBytes(6)============capacity(): 64maxCapacity(): 200readerIndex(): 0readableBytes(): 10isReadable(): truewriterIndex(): 10writableBytes(): 54isWritable(): truemaxWritableBytes(): 190getByte(3) return: 4getShort(3) return: 1024getInt(3) return: 67108864after ===========getByte()============capacity(): 64maxCapacity(): 200readerIndex(): 0readableBytes(): 10isReadable(): truewriterIndex(): 10writableBytes(): 54isWritable(): truemaxWritableBytes(): 190after ===========setByte()============capacity(): 64maxCapacity(): 200readerIndex(): 0readableBytes(): 10isReadable(): truewriterIndex(): 10writableBytes(): 54isWritable(): truemaxWritableBytes(): 190after ===========readBytes(10)============capacity(): 64maxCapacity(): 200readerIndex(): 10readableBytes(): 0isReadable(): falsewriterIndex(): 10writableBytes(): 54isWritable(): truemaxWritableBytes(): 190總結
本小節 , 我們分析了Netty對二進制數據的抽象ByteBuf結構 , 本質上他的原理就是 , 他引用了一段內存 , 這段內存可以是對內的也可以是堆外的 , 然后引用計數來控制內存是否需要被釋放 , 使用讀寫指針來控制ByteBuf的讀寫 , 可以理解為是外觀模式的一種使用
基于讀寫指針和容量、最大可擴容容量,衍生出一系列的讀寫方法,要注意 read/write 與 get/set 的區別
多個 ByteBuf 可以引用同一段內存,通過引用計數來控制內存的釋放,遵循誰 retain() 誰 release() 的原則
最后,我們通過一個具體的例子說明 ByteBuf 的實際使用
思考:
總結
以上是生活随笔為你收集整理的Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty实战 IM即时通讯系统(六)实
- 下一篇: Netty实战 IM即时通讯系统(八)服