Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍
##
Netty實(shí)戰(zhàn) IM即時(shí)通訊系統(tǒng)(七)數(shù)據(jù)傳輸載體ByteBuf介紹零、 目錄
- 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)
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();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ì)象
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í)際使用
思考:
總結(jié)
以上是生活随笔為你收集整理的Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty实战 IM即时通讯系统(六)实
- 下一篇: Netty实战 IM即时通讯系统(八)服