1.首先對于MessageToByteEncoder 其實(shí)沒啥太多亮點(diǎn) 他就是將我們的message轉(zhuǎn)換成byte,主要的亮點(diǎn)還是在ByteToMessageDecoder即解碼。
2.ByteToMessageDecoder是netty解碼的一個(gè)抽象類,其實(shí)現(xiàn)了channelRead方法,而實(shí)現(xiàn)者只要實(shí)現(xiàn)其decode方法即可。
3.那ByteToMessageDecoder的channelRead方法幫我們解決了什么問題?其實(shí)就是幫我得到一個(gè)完整的數(shù)據(jù)包,假如我們傳遞了100個(gè)字節(jié),可能接被底層tcp分為三個(gè)數(shù)據(jù)包發(fā)送(30,30,40)或者出現(xiàn)粘包情況一次性傳遞200字節(jié)的數(shù)據(jù),所以我們需要一個(gè)解碼器能識別一個(gè)完整的數(shù)據(jù)包即可,而MessageToByteEncoder就是做了這樣的工作。
4.我們判斷一個(gè)數(shù)據(jù)包是否完整有多種方式,再頭部放置該數(shù)據(jù)包的具體長度,或者數(shù)據(jù)包中以特殊的標(biāo)識符為記號。目前主流的是前者而且netty已經(jīng)幫我們實(shí)現(xiàn)了---LengthFieldBasedFrameDecoder。下面我們就是是從數(shù)據(jù)整個(gè)解碼流程開始談起從ByteToMessageDecoder到--LengthFieldBasedFrameDecoder。
5.ByteToMessageDecoder的channelread的邏輯是:如果當(dāng)前的對象是byteBuf則可以處理,非bytebuf的直接交給下一個(gè)handler。
6.從threadlocal獲取存放每一次解碼之后的結(jié)果的對象CodecOutputList,查看我們的bytebuf類型的cumulation是否為空,若為空代表這是當(dāng)前這個(gè)消息的第一次解碼,不為空則代表當(dāng)前這個(gè)數(shù)據(jù)需要和前幾次接受到的數(shù)據(jù)一起進(jìn)行解碼。
7.而上述合并cumulation是通過cumulator進(jìn)行操作的,而cumulator有兩種實(shí)現(xiàn)方式,一個(gè)是把所有的bytebuf積攢到一個(gè)大的bytebuf里面,第二種方式就是創(chuàng)建一個(gè)compositeByteBuf。前者需要內(nèi)存之間拷貝后者并不需要,netty默認(rèn)使用前者。
8.接下來就是調(diào)用callDecode去循環(huán)解碼,這個(gè)我們稍后解析。我們先解析當(dāng)callDecode執(zhí)行完畢之后回去執(zhí)行其finally方法,該方法主要就是先檢測當(dāng)前的cumulation不為空且又不能讀取那么就直接釋放,也會進(jìn)行次數(shù)判斷即當(dāng)前的數(shù)據(jù)包 我們channelread讀取了次數(shù)超過discardAfterReads了則 我們需要拋棄已讀數(shù)據(jù),這樣能節(jié)省內(nèi)存避免我們發(fā)生oom異常,但是因?yàn)樵摲椒ㄐ枰獢?shù)組拷貝,所以不能常用。
9.那么會發(fā)生discardAfterReads的情況有哪些尼,試想下我們編碼時(shí)候告訴我解碼器 我會傳送100個(gè)G給你,那么這個(gè)channelhandler得需要存儲100g內(nèi)容在jvm中(或者堆外內(nèi)存),如果我們得內(nèi)存不夠大則會拋出oom異常,所以這邊做了一個(gè)優(yōu)化,連續(xù)多次讀取還未讀取到一個(gè)完整得數(shù)據(jù)包 則丟棄一些已經(jīng)read的字節(jié) 這樣就可以重復(fù)寫。當(dāng)然discardSomeReadBytes得邏輯就是將我們已經(jīng)reade的部分(并不是全部)給拋棄如果我們沒有read則不拋棄。
10.我們的callDecode邏輯是:循環(huán)判斷cumulation是否可讀,然后判斷我們的是否已經(jīng)又部分解碼好的內(nèi)容,如果有則傳遞到下一個(gè)channelhandler,但是我們bytetomessage一般都是解碼一個(gè)完整的數(shù)據(jù)包發(fā)送到下一個(gè)。
11.我們會先獲取我們的當(dāng)前的cumulation可讀的字節(jié)大小,然后調(diào)用子類去進(jìn)行解碼(也就是我們的這邊的LengthFieldBasedFrameDecoder)。
12.如果我們發(fā)現(xiàn)我們的cummulation子類沒有讀取那么我們就跳出循環(huán),子類沒有讀取的原因大概率是因?yàn)檫@不是一個(gè)完整的數(shù)據(jù)包,具體的我們會在下面分析LengthFieldBasedFrameDecoder的時(shí)候說明
13.如果成功讀取則我們會最終傳遞到下一個(gè)channelhandler。那么我下面就要將LengthFieldBasedFrameDecoder的解碼邏輯。
14.LengthFieldBasedFrameDecoder的邏輯大致就是我們預(yù)先設(shè)定我們這個(gè)類最大能接受多少字節(jié)的內(nèi)容進(jìn)行解碼,以及是否需要在解碼初期跳過一些字節(jié),最關(guān)鍵的就是lengthFieldOffset和lengthFieldLength參數(shù),前者代表我們從這個(gè)字節(jié)中的第幾個(gè)字節(jié)開始獲取這個(gè)完整數(shù)據(jù)包的長度,后者代表我們標(biāo)識長度的值用幾個(gè)字節(jié)標(biāo)識。比如lengthFieldOffset=0,lengthFieldLength=4則代表我們要從第一個(gè)字節(jié)開始獲取4個(gè)字節(jié),這四個(gè)字節(jié)的值就是整個(gè)數(shù)據(jù)包的長度。注意這個(gè)長度僅僅包含真正的數(shù)據(jù)內(nèi)容的長度不好包含長度值本身的字節(jié)大小。我們知道一個(gè)字節(jié)等于8為那么4個(gè)字節(jié)就是32位。如果我們數(shù)據(jù)長度較大,那么可以設(shè)置為Long即64位。
15.每次LengthFieldBasedFrameDecoder都是將整個(gè)字節(jié)長度和解碼得到的字節(jié)長度值進(jìn)行比較,如果不一致則不進(jìn)行解碼。直到接受的數(shù)據(jù)大于我們設(shè)置的長度值,我們才開始解碼并返回解碼值。
16.好了說完上面我們細(xì)致的說下LengthFieldBasedFrameDecoder的解碼細(xì)致流程。
17.首先判斷discardingTooLongFrame是否等于true,如果是true代表netty在上一次檢測到即將傳遞的數(shù)據(jù)大小超過我們設(shè)置的解碼最大值,那么我們需要整個(gè)數(shù)據(jù)包都拋棄,而上一次只是拋棄一部分,還有剩余的需要繼續(xù)拋棄。
18.然后根據(jù)failFast來判斷是在拋棄所有數(shù)據(jù)完成之后就拋出異常,還是只要檢測到超出就拋出異常。
19.如果discardingTooLongFrame為false ,我們接下來需要判斷我們傳遞過來的數(shù)據(jù)的字節(jié)長度是否超過lengthFieldEndOffset,如果沒有超過代表我們還無法獲取即將傳遞過來的數(shù)據(jù)的長度,那么我們直接結(jié)束編碼。
20.如果成功獲取到即將要傳遞過來的字節(jié)長度然后再加上字節(jié)長度值本身的字節(jié)大小得到一個(gè)總的frameLength,我們拿這個(gè)frameLength分別和lengthFieldEndOffset以及maxFrameLength進(jìn)行比較,前者是判斷如果得到數(shù)據(jù)包的長度還小于我們的lengthFieldEndOffset,那么說明我們沒有沒有數(shù)據(jù)傳遞或者offset搞錯(cuò)了 ,直接拋出異常。
21.如果我們frameLength大于maxFrameLength,則該數(shù)據(jù)包會被完全拋棄掉并拋出異常。
22.如果上面都沒有問題 我們再判斷我們收集到的數(shù)據(jù)是否大于等于frameLength,如果小于說明不是一個(gè)完整的包。直接返回等待下次接收到剩余的數(shù)據(jù)包再進(jìn)行解碼。
23.最后再跳過一些initialBytesToStrip,如果還滿足成功條件 則我們就進(jìn)行解碼,從cummlation中抽取一個(gè)完整的數(shù)據(jù)包。
---即bytebuf,然后傳遞到其他的channelhandler進(jìn)行處理。24.整個(gè)解碼過程大致說完,netty系列即將要結(jié)束 如果有問題請留言!!
ByteToMessageDecoder的原理總結(jié)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 1、ChannelHandler源碼解析 1.1、ChannelHandler功能說明 ChannelHandle...
- 編解碼處理器作為Netty編程時(shí)必備的ChannelHandler,每個(gè)應(yīng)用都必不可少。Netty作為網(wǎng)絡(luò)應(yīng)用框架...
- 前言 問題 現(xiàn)如今我們使用通用的應(yīng)用程序或者類庫來實(shí)現(xiàn)系統(tǒng)之間地互相訪問。例如,我們經(jīng)常使用一個(gè)HTTP客戶端來從...
- 一、粘包與拆包 1、發(fā)送時(shí)的粘包與拆包 TCP連接維護(hù)了一個(gè)發(fā)送緩存區(qū)。將要發(fā)送給對端的數(shù)據(jù)會由socket AP...
- 原文地址:http://netty.io/wiki/reference-counted-objects.html ...