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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

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

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

##

Netty實(shí)戰(zhàn) IM即時(shí)通訊系統(tǒng)(七)數(shù)據(jù)傳輸載體ByteBuf介紹

零、 目錄

  • IM系統(tǒng)簡(jiǎn)介
    • Netty 簡(jiǎn)介
    • Netty 環(huán)境配置
    • 服務(wù)端啟動(dòng)流程
    • 客戶端啟動(dòng)流程
    • 實(shí)戰(zhàn): 客戶端和服務(wù)端雙向通信
    • 數(shù)據(jù)傳輸載體ByteBuf介紹
    • 客戶端與服務(wù)端通信協(xié)議編解碼
    • 實(shí)現(xiàn)客戶端登錄
    • 實(shí)現(xiàn)客戶端與服務(wù)端收發(fā)消息
    • pipeline與channelHandler
    • 構(gòu)建客戶端與服務(wù)端pipeline
    • 拆包粘包理論與解決方案
    • channelHandler的生命周期
    • 使用channelHandler的熱插拔實(shí)現(xiàn)客戶端身份校驗(yàn)
    • 客戶端互聊原理與實(shí)現(xiàn)
    • 群聊的發(fā)起與通知
    • 群聊的成員管理(加入與退出,獲取成員列表)
    • 群聊消息的收發(fā)及Netty性能優(yōu)化
    • 心跳與空閑檢測(cè)
    • 總結(jié)
    • 擴(kuò)展

    七、 數(shù)據(jù)傳輸載體ByteBuf 介紹

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

  • ByteBuf結(jié)構(gòu)

  • 首先我們來了解一下ByteBuf 結(jié)構(gòu)
  • 以上就是一個(gè)ByteBuf 的結(jié)構(gòu)圖 , 從上面這幅圖中可以看到:
  • ByteBuf 是一個(gè)字節(jié)容器 , 容器里的數(shù)據(jù)分為三部分:
  • 第一部分是已經(jīng)丟棄的字節(jié) , 這部分?jǐn)?shù)據(jù)時(shí)無效的
  • 第二部分是可讀字節(jié) , 這部分?jǐn)?shù)據(jù)是ByteBuf 的主體數(shù)據(jù) , 從ByteBuf里面讀取的數(shù)據(jù)來自這一部分
  • 最后一部分虛線表示的是該ByteBuf 最多還能擴(kuò)容多少容量
  • 以上三段內(nèi)容是被兩個(gè)指針給劃分出來的 , 從左到右 , 依次是 讀指針(readIndex) 、 寫指針(writeIndex) , 然后還有一個(gè)變量capacity , 表示ByteBuf底層內(nèi)存的總?cè)萘?/li>
  • 從ByteBuf 中每讀取一個(gè)字節(jié) , readIndex自增1 , ByteBuf里面總共有writeIndez-readIndex個(gè)字節(jié)可讀 , 由此可以得知 writeIndex = readIndex 時(shí) , ByteBuf 不可讀
  • 寫數(shù)據(jù)是從writeIndex 指向的部分開始寫 , 每寫一個(gè)字節(jié)writeIndex自增1 , 直到增大到capacity , 這個(gè)時(shí)候表示ByteBuff不可寫了
  • ByteBuf 里面其實(shí)還有一個(gè)參數(shù)maxCapacityv, 當(dāng)向ByteBuf寫數(shù)據(jù)的時(shí)候 , 如果容量不足 , 那么這個(gè)時(shí)候可以進(jìn)行擴(kuò)容 , 直到capacity擴(kuò)容到macCapacity , 超過MaxCapacity 就會(huì)報(bào)錯(cuò)
  • Netty使用這個(gè)數(shù)據(jù)結(jié)構(gòu)可以有效的區(qū)分可讀數(shù)據(jù)和可寫數(shù)據(jù) , 讀寫之間相互沒有沖突 , 當(dāng)然 , ByteBuf 只是對(duì)二進(jìn)制數(shù)據(jù)的抽象 , 具體底層實(shí)現(xiàn)我們?cè)谙旅娴男」?jié)講到 , 這一小節(jié)我們只要知道Netty關(guān)于數(shù)據(jù)讀寫之人ByteBuf , 下面我們就來學(xué)習(xí)一下BuyteBuf 的常用API
  • Api

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

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

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

    // 代碼片段1int readerIndex = buffer.readerIndex();// .. 其他操作buffer.readerIndex(readerIndex);// 代碼片段二buffer.markReaderIndex();// .. 其他操作buffer.resetReaderIndex();
  • 推薦使用代碼片段二這種方式 , 不需要自己定義變量
  • 讀寫API
  • writeBytes(byte[] src) 與 buffer.readBytes(byte[] dst) :writeBytes(bs) 表示把字節(jié)數(shù)組bs中的字節(jié)全部寫到ByteBuf , 而readBytes()指的是把ByteBuf里的數(shù)據(jù)全部讀取到dst , 這里dst的字節(jié)數(shù)組的大小通常等于readableBytes() , 而src的長度通常小于writeableBytes();

  • writeByte(byte b) 與 readByte():writeByte()表示往ByteBuf中寫入一個(gè)字節(jié) , 類似的API還有writeBoolean() , writeChar() , writeShort()、writeLong()、writeFloat()、writeDouble() 與 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() 這里就不一一贅述了,相信讀者應(yīng)該很容易理解這些 API 。 與讀寫 API 類似的 API 還有 getBytes、getByte() 與 setBytes()、setByte() 系列,唯一的區(qū)別就是 get/set 不會(huì)改變讀寫指針,而 read/write 會(huì)改變讀寫指針,這點(diǎn)在解析數(shù)據(jù)的時(shí)候千萬要注意

  • release() 與 retain(): 由于 Netty 使用了堆外內(nèi)存,而堆外內(nèi)存是不被 jvm 直接管理的,也就是說申請(qǐng)到的內(nèi)存無法被垃圾回收器直接回收,所以需要我們手動(dòng)回收。有點(diǎn)類似于c語言里面,申請(qǐng)到的內(nèi)存必須手工釋放,否則會(huì)造成內(nèi)存泄漏。Netty 的 ByteBuf 是通過引用計(jì)數(shù)的方式管理的,如果一個(gè) ByteBuf 沒有地方被引用到,需要回收底層內(nèi)存。默認(rèn)情況下,當(dāng)創(chuàng)建完一個(gè) ByteBuf,它的引用為1,然后每次調(diào)用 retain() 方法, 它的引用就加一, release() 方法原理是將引用計(jì)數(shù)減一,減完之后如果發(fā)現(xiàn)引用計(jì)數(shù)為0,則直接回收 ByteBuf 底層的內(nèi)存。

  • slice()、duplicate()、copy():這三個(gè)方法通常情況會(huì)放到一起比較,這三者的返回值都是一個(gè)新的 ByteBuf 對(duì)象

  • slice() 方法從原始 ByteBuf 中截取一段,這段數(shù)據(jù)是從 readerIndex 到 writeIndex,同時(shí),返回的新的 ByteBuf 的最大容量 maxCapacity 為原始 ByteBuf 的 readableBytes()
  • duplicate() 方法把整個(gè) ByteBuf 都截取出來,包括所有的數(shù)據(jù),指針信息
  • slice() 方法與 duplicate() 方法的相同點(diǎn)是:底層內(nèi)存以及引用計(jì)數(shù)與原始的 ByteBuf 共享,也就是說經(jīng)過 slice() 或者 duplicate() 返回的 ByteBuf 調(diào)用 write 系列方法都會(huì)影響到 原始的 ByteBuf,但是它們都維持著與原始 ByteBuf 相同的內(nèi)存引用計(jì)數(shù)和不同的讀寫指針
  • slice() 方法與 duplicate() 不同點(diǎn)就是:slice() 只截取從 readerIndex 到 writerIndex 之間的數(shù)據(jù),它返回的 ByteBuf 的最大容量被限制到 原始 ByteBuf 的 readableBytes(), 而 duplicate() 是把整個(gè) ByteBuf 都與原始的 ByteBuf 共享
  • slice() 方法與 duplicate() 方法不會(huì)拷貝數(shù)據(jù),它們只是通過改變讀寫指針來改變讀寫的行為,而最后一個(gè)方法 copy() 會(huì)直接從原始的 ByteBuf 中拷貝所有的信息,包括讀寫指針以及底層對(duì)應(yīng)的數(shù)據(jù),因此,往 copy() 返回的 ByteBuf 中寫數(shù)據(jù)不會(huì)影響到原始的 ByteBuf
  • slice() 和 duplicate() 不會(huì)改變 ByteBuf 的引用計(jì)數(shù),所以原始的 ByteBuf 調(diào)用 release() 之后發(fā)現(xiàn)引用計(jì)數(shù)為零,就開始釋放內(nèi)存,調(diào)用這兩個(gè)方法返回的 ByteBuf 也會(huì)被釋放,這個(gè)時(shí)候如果再對(duì)它們進(jìn)行讀寫,就會(huì)報(bào)錯(cuò)。因此,我們可以通過調(diào)用一次 retain() 方法 來增加引用,表示它們對(duì)應(yīng)的底層的內(nèi)存多了一次引用,引用計(jì)數(shù)為2,在釋放內(nèi)存的時(shí)候,需要調(diào)用兩次 release() 方法,將引用計(jì)數(shù)降到零,才會(huì)釋放內(nèi)存
  • 這三個(gè)方法均維護(hù)著自己的讀寫指針,與原始的 ByteBuf 的讀寫指針無關(guān),相互之間不受影響
  • retainedSlice() 與 retainedDuplicate():相信讀者應(yīng)該已經(jīng)猜到這兩個(gè) API 的作用了,它們的作用是在截取內(nèi)存片段的同時(shí),增加內(nèi)存的引用計(jì)數(shù),分別與下面兩段代碼等價(jià)

    // retainedSlice 等價(jià)于slice().retain();// retainedDuplicate() 等價(jià)于
  • 使用到 slice 和 duplicate 方法的時(shí)候,千萬要理清內(nèi)存共享,引用計(jì)數(shù)共享,讀寫指針不共享幾個(gè)概念,下面舉兩個(gè)常見的易犯錯(cuò)的例子

    多次釋放Buffer buffer = xxx;doWith(buffer);// 一次釋放buffer.release();public void doWith(Bytebuf buffer) {// ... // 沒有增加引用計(jì)數(shù)Buffer slice = buffer.slice();foo(slice);}public void foo(ByteBuf buffer) {// read from buffer// 重復(fù)釋放buffer.release();}這里的 doWith 有的時(shí)候是用戶自定義的方法,有的時(shí)候是 Netty 的回調(diào)方法,比如 channelRead() 等等不釋放造成內(nèi)存泄漏Buffer buffer = xxx;doWith(buffer);// 引用計(jì)數(shù)為2,調(diào)用 release 方法之后,引用計(jì)數(shù)為1,無法釋放內(nèi)存 buffer.release();public void doWith(Bytebuf buffer) {// ... // 增加引用計(jì)數(shù)Buffer slice = buffer.retainedSlice();foo(slice);// 沒有調(diào)用 release}public void foo(ByteBuf buffer) {// read from buffer}
  • 實(shí)戰(zhàn):

  • 代碼

    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 的時(shí)候,buffer 仍然可寫buffer.writeBytes(new byte[] { 1, 2, 3, 4 });print("writeBytes(1,2,3,4)", buffer);// write 方法改變寫指針,寫完之后寫指針未到 capacity 的時(shí)候,buffer 仍然可寫, 寫完 int 類型之后,寫指針增加4buffer.writeInt(10);print("writeInt(10)", buffer);// write 方法改變寫指針, 寫完之后寫指針等于 capacity 的時(shí)候,buffer 不可寫buffer.writeBytes(new byte[] { 5 });print("writeBytes(5)", buffer);// write 方法改變寫指針,寫的時(shí)候發(fā)現(xiàn) buffer 不可寫則開始擴(kuò)容,擴(kuò)容之后 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();}}執(zhí)行結(jié)果: 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
  • 總結(jié)

  • 本小節(jié) , 我們分析了Netty對(duì)二進(jìn)制數(shù)據(jù)的抽象ByteBuf結(jié)構(gòu) , 本質(zhì)上他的原理就是 , 他引用了一段內(nèi)存 , 這段內(nèi)存可以是對(duì)內(nèi)的也可以是堆外的 , 然后引用計(jì)數(shù)來控制內(nèi)存是否需要被釋放 , 使用讀寫指針來控制ByteBuf的讀寫 , 可以理解為是外觀模式的一種使用

  • 基于讀寫指針和容量、最大可擴(kuò)容容量,衍生出一系列的讀寫方法,要注意 read/write 與 get/set 的區(qū)別

  • 多個(gè) ByteBuf 可以引用同一段內(nèi)存,通過引用計(jì)數(shù)來控制內(nèi)存的釋放,遵循誰 retain() 誰 release() 的原則

  • 最后,我們通過一個(gè)具體的例子說明 ByteBuf 的實(shí)際使用

  • 思考:

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

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。