日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍

發布時間:2024/4/30 windows 76 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

##

Netty實戰 IM即時通訊系統(七)數據傳輸載體ByteBuf介紹

零、 目錄

  • IM系統簡介
    • Netty 簡介
    • Netty 環境配置
    • 服務端啟動流程
    • 客戶端啟動流程
    • 實戰: 客戶端和服務端雙向通信
    • 數據傳輸載體ByteBuf介紹
    • 客戶端與服務端通信協議編解碼
    • 實現客戶端登錄
    • 實現客戶端與服務端收發消息
    • pipeline與channelHandler
    • 構建客戶端與服務端pipeline
    • 拆包粘包理論與解決方案
    • channelHandler的生命周期
    • 使用channelHandler的熱插拔實現客戶端身份校驗
    • 客戶端互聊原理與實現
    • 群聊的發起與通知
    • 群聊的成員管理(加入與退出,獲取成員列表)
    • 群聊消息的收發及Netty性能優化
    • 心跳與空閑檢測
    • 總結
    • 擴展

    七、 數據傳輸載體ByteBuf 介紹

  • 前面的小節中我們了解到Netty的數據讀寫都是以ByteBuf 為單位進行交互的 , 我們接下來就剖析一下ByteBuf

  • ByteBuf結構

  • 首先我們來了解一下ByteBuf 結構
  • 以上就是一個ByteBuf 的結構圖 , 從上面這幅圖中可以看到:
  • ByteBuf 是一個字節容器 , 容器里的數據分為三部分:
  • 第一部分是已經丟棄的字節 , 這部分數據時無效的
  • 第二部分是可讀字節 , 這部分數據是ByteBuf 的主體數據 , 從ByteBuf里面讀取的數據來自這一部分
  • 最后一部分虛線表示的是該ByteBuf 最多還能擴容多少容量
  • 以上三段內容是被兩個指針給劃分出來的 , 從左到右 , 依次是 讀指針(readIndex) 、 寫指針(writeIndex) , 然后還有一個變量capacity , 表示ByteBuf底層內存的總容量
  • 從ByteBuf 中每讀取一個字節 , readIndex自增1 , ByteBuf里面總共有writeIndez-readIndex個字節可讀 , 由此可以得知 writeIndex = readIndex 時 , ByteBuf 不可讀
  • 寫數據是從writeIndex 指向的部分開始寫 , 每寫一個字節writeIndex自增1 , 直到增大到capacity , 這個時候表示ByteBuff不可寫了
  • ByteBuf 里面其實還有一個參數maxCapacityv, 當向ByteBuf寫數據的時候 , 如果容量不足 , 那么這個時候可以進行擴容 , 直到capacity擴容到macCapacity , 超過MaxCapacity 就會報錯
  • Netty使用這個數據結構可以有效的區分可讀數據和可寫數據 , 讀寫之間相互沒有沖突 , 當然 , ByteBuf 只是對二進制數據的抽象 , 具體底層實現我們在下面的小節講到 , 這一小節我們只要知道Netty關于數據讀寫之人ByteBuf , 下面我們就來學習一下BuyteBuf 的常用API
  • Api

  • 容量API
  • capacity(): 表示ByteBuf底層占用了多少字節的內存(包括 丟棄的字節 , 可讀的字節 , 可寫的字節) , 不同的底層實現由不同的算法機制 , 后面我們將|ByteBuf 分類的時候會講到
  • maxCapacity():表示ByteBuf 最大能占用多少字節的內存 , 當向ByteBuf寫數據時 , 如果發現容量不夠會進行擴容 , 直到擴容到macCapacity , 超過這個數 , 就拋異常。
  • readableBytes() 與 isReadable():readableBytes()返回可讀的字節數 , 他的值等于writeIndex - readIndex , 如果兩者相等 , 則不可讀 , 這時isReadable()返回false
  • writableBytes()、 isWritable() 與 maxWritableBytes() : writeable() 返回當前可寫的字節數 , 他的值等于 capacity - writeable 如果兩者相等 , 則表示不可寫 , 這個時候isWriteable()返回為false , 但是這個時候并代表不能忘ByteBuf 中填充數據了 , 如果發現往ByteBuf 中寫數據寫不進去的話 , Netty會自動擴容ByteBuf , 直到擴容到底層的內存大小為maxCapacity , 而maxCapacity()就表示可寫的最大字節數 , 他的值就等于maxCapacity
  • 讀寫指針相關的API
  • readerIndex() 和 readerIndex(int): 前者表示獲取當前讀指針位置 , 后者表示設置讀指針位置

  • writeIndex() 與 writeIndex(int): 前者表示獲取當前寫指針位置 , 后者表示設置寫指針位置

  • markReaderIndex() 與 resetReaderIndex(): 前者表示把當前的讀指針保存起來 , 后者表示 把當前的讀指針恢復到之前保存的值

    // 代碼片段1int readerIndex = buffer.readerIndex();// .. 其他操作buffer.readerIndex(readerIndex);// 代碼片段二buffer.markReaderIndex();// .. 其他操作buffer.resetReaderIndex();
  • 推薦使用代碼片段二這種方式 , 不需要自己定義變量
  • 讀寫API
  • 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 對象

  • slice() 方法從原始 ByteBuf 中截取一段,這段數據是從 readerIndex 到 writeIndex,同時,返回的新的 ByteBuf 的最大容量 maxCapacity 為原始 ByteBuf 的 readableBytes()
  • duplicate() 方法把整個 ByteBuf 都截取出來,包括所有的數據,指針信息
  • slice() 方法與 duplicate() 方法的相同點是:底層內存以及引用計數與原始的 ByteBuf 共享,也就是說經過 slice() 或者 duplicate() 返回的 ByteBuf 調用 write 系列方法都會影響到 原始的 ByteBuf,但是它們都維持著與原始 ByteBuf 相同的內存引用計數和不同的讀寫指針
  • slice() 方法與 duplicate() 不同點就是:slice() 只截取從 readerIndex 到 writerIndex 之間的數據,它返回的 ByteBuf 的最大容量被限制到 原始 ByteBuf 的 readableBytes(), 而 duplicate() 是把整個 ByteBuf 都與原始的 ByteBuf 共享
  • slice() 方法與 duplicate() 方法不會拷貝數據,它們只是通過改變讀寫指針來改變讀寫的行為,而最后一個方法 copy() 會直接從原始的 ByteBuf 中拷貝所有的信息,包括讀寫指針以及底層對應的數據,因此,往 copy() 返回的 ByteBuf 中寫數據不會影響到原始的 ByteBuf
  • slice() 和 duplicate() 不會改變 ByteBuf 的引用計數,所以原始的 ByteBuf 調用 release() 之后發現引用計數為零,就開始釋放內存,調用這兩個方法返回的 ByteBuf 也會被釋放,這個時候如果再對它們進行讀寫,就會報錯。因此,我們可以通過調用一次 retain() 方法 來增加引用,表示它們對應的底層的內存多了一次引用,引用計數為2,在釋放內存的時候,需要調用兩次 release() 方法,將引用計數降到零,才會釋放內存
  • 這三個方法均維護著自己的讀寫指針,與原始的 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 的實際使用

  • 思考:

  • slice 方法可能用在什么場景?歡迎留言討論。
  • 在哪種場景下需要我們調用retain()去增加引用計數呢?
  • 比如,你抽象出來的一個方法,這個功能就是把bytebuf轉換成一個對象,然后release,如果你想調用這個方法之后還想繼續讀數據,那么久需要在調用這個方法前 retain一下
  • ByteBuf引用的內存也可以是堆內的嗎?怎么指定堆內?
  • 分配內存的時候可以調用分配堆內存的方法,ByteBufAllocator.heapBuffer() , r如果使用了堆內內存 , 則不需要手動釋放
  • 擴容每次擴多少?
  • 從64B開始,指數擴容,直到能裝下為止
  • 為什么getshort(3) 是 1024?
  • 執行了buffer.writeBytes(new byte[]{1, 2, 3, 4});后,往buffer里寫了4個byte,再執行buffer.writeInt(12);后,因為int長度為4 bytes,所以又往buffer里寫了4個byte,總共寫入8個byte。而getshort(3)是從第4個byte開始,一共讀取2個byte(即第4和第5個),其二進制表示為 0000 0100 0000 0000,變成十進制就是1024
  • 總結

    以上是生活随笔為你收集整理的Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。