Netty解碼器LengthFieldBasedFrameDecoder詳解
Netty解碼器LengthFieldBasedFrameDecoder
解碼器LengthFieldBasedFrameDecoder,從名字上可以猜測出來,它是基于長度的解碼器.
Netty從TCP緩沖區(qū)中讀取字節(jié),把這些字節(jié)交給LengthFieldBasedFrameDecoder進行解碼,解碼的操作是根據(jù)設(shè)定的規(guī)則,根據(jù)規(guī)則,從字節(jié)中解碼出來有意義的數(shù)據(jù),然后把數(shù)據(jù)再交給后續(xù)的Handler處理.
接下來看它是如何根據(jù)規(guī)則解碼的

如上圖,從網(wǎng)絡(luò)中讀取到的數(shù)據(jù)是基于流的,而且是有方向的.
然而數(shù)據(jù)是沒有邊界的,不知道從哪兒到哪兒是一個完整的數(shù)據(jù),下一個數(shù)據(jù)又是從哪個到哪個.
因此應(yīng)用層需要設(shè)定規(guī)則,根據(jù)規(guī)則就可以知道數(shù)據(jù)的邊界在哪兒.

如上圖,便是根據(jù)設(shè)定的規(guī)則,就可以’篩選’出來真正有意義的數(shù)據(jù)(data)在哪個. 而且允許每個data的長度是不一樣大小.
就要說下這個規(guī)則是什么了
規(guī)則是由4個主要的屬性構(gòu)成,lengthFieldOffset,lengthFieldLength,lengthAdjustment,initialBytesToStrip.
通過一個數(shù)據(jù)塊為例介紹這4個屬性.

如上圖,從紅色箭頭指向的位置開始讀取數(shù)據(jù).
lengthFieldOffset表示長度字段的偏移量,經(jīng)過lengthFieldOffset之后,箭頭指向了下一個位置.
如果lengthFieldOffset=3,那么箭頭需要向右邊走3個字節(jié).

接下來,lengthFieldLength表示長度字段的長度(好繞口).
如果lengthFieldLength=4,那么就會從上圖紅色位置向后讀取4個字節(jié),把4個字節(jié)里面的內(nèi)容作為真正data的長度.
而且lengthFieldLength的取值不是任意的,它只能取值1,2,3,4,8. 具體原因后面的源碼會說明.

如上圖,假如lengthFieldLength=4,讀取4個字節(jié)的內(nèi)容是0x00000010(十六進制表示),十進制就是16,也就是說,數(shù)據(jù)data的長度是16個字節(jié). 但是這里稍等下,需要介紹下一個關(guān)鍵屬性.
lengthAdjustment表示長度調(diào)整. 調(diào)整什么呢? 還是要說下lengthFieldLength. lengthFieldLength里面的內(nèi)容是16,雖然這個16表示長度,但是它是表示真正數(shù)據(jù)data的長度,還是表示整個的長度呢,或者其他呢. 因此要想真正表示真正數(shù)據(jù)data的長度,必須用lengthFieldLength的內(nèi)容值+lengthAdjustment的值.
如果lengthAdjustment=-5,也就是用16+(-5)=11,即從上圖紅色位置繼續(xù)向后讀取11個字節(jié)才能真正的把數(shù)據(jù)讀取完整,讀取少了或多了都不行.
到這里,已經(jīng)把一個完整的數(shù)據(jù)塊讀取完成了. 但是呢,真正表示業(yè)務(wù)數(shù)據(jù)的內(nèi)容是data部分.我們不想要前面的lengthFieldOffset和lengthFieldLength部分,這里就需要使用initialBytesToStrip. 它表示跳過多少字節(jié).
如果initialBytesToStrip=7,那么就是說要跳過7個字節(jié),把剩余部分傳給下游的Handler繼續(xù)處理.

以上就是4個主要屬性的解釋,從源碼中拿一個具體的’案例’再溫習下.

從最左邊開始讀取數(shù)據(jù),lengthFieldOffset=1,那么向后讀取1個字節(jié),lengthFieldLength=2,向后讀取2個字節(jié),讀取到的內(nèi)容是0x0010(十六進制),十進制就是16,由于lengthAdjustment=-3,因此16+(-3)=13,于是繼續(xù)向后讀取13個字節(jié).
就會把0xFE和"HELLO,WORLD"這13個字節(jié)讀取到. 到目前為止,讀取到的內(nèi)容是0xCA0010FE和"HELLO,WORLD"共16個字節(jié). 又initialBytesToStrip=3,因此從16個字節(jié)的開頭跳過3個字節(jié),跳過了0xCA0010這3個字節(jié),最后剩下0xFE和"HELLO,WORLD"傳給了下游的Handler.
源碼解讀如下,下面可以不看
Netty的源碼位置 io.netty.handler.codec.LengthFieldBasedFrameDecoder

LengthFieldBasedFrameDecoder繼承了ChannelInboundHandlerAdapter,因此當Netty讀取到網(wǎng)絡(luò)數(shù)據(jù)之后,再向下傳播數(shù)據(jù)的過程中,會調(diào)用到ByteToMessageDecoder的channelRead方法,channelRead方法內(nèi)部會調(diào)用decode方法.
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
// 由于整個幀的長度 frameLength 大于 設(shè)定的maxFrameLength, 是需要跳過這個無效幀的.
// 之前已經(jīng)跳過了一部分數(shù)據(jù), 由于之前不夠跳過, 現(xiàn)在又讀取到了數(shù)據(jù), 那么需要繼續(xù)跳過剩下'欠'的數(shù)據(jù)
if (discardingTooLongFrame) {
discardingTooLongFrame(in);
}
// 在構(gòu)造函數(shù)中定義了 lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength
// 它的意思是目前可以從網(wǎng)絡(luò)中讀取的實際字節(jié)數(shù)(in.readableBytes()) 小于 lengthFieldEndOffset , 無法處理 直接返回, 需要等待更多的數(shù)據(jù).
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
// in.readerIndex() 表示讀取上一個完整數(shù)據(jù)的最后下標
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
// frameLength 表示獲取沒有調(diào)整前的數(shù)據(jù)幀長度 . 后面的邏輯要使用lengthAdjustment屬性調(diào)整成真實的數(shù)據(jù)幀長度
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
// 如果讀取到的frameLength 小于 0, 說明此數(shù)據(jù)有問題, 拋出異常
if (frameLength < 0) {
failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
}
// 調(diào)整frameLength長度 frameLength = frameLength + lengthAdjustment + lengthFieldEndOffset
// frameLength + lengthAdjustment 表示真實數(shù)據(jù)的長度
// 即這里的frameLength 就是整個數(shù)據(jù)的長度(包括真實數(shù)據(jù)).
frameLength += lengthAdjustment + lengthFieldEndOffset;
// 如果frameLength < lengthFieldEndOffset 那只能說明在上面的計算過程中, frameLength + lengthAdjustment < 0 了.
// frameLength + lengthAdjustment 表示真實數(shù)據(jù)的長度, 數(shù)據(jù)的長度怎么會小于0呢 因此拋異常.
if (frameLength < lengthFieldEndOffset) {
failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
}
// 如果整個數(shù)據(jù)的長度大于設(shè)定的最大值. 那么認為這是無效數(shù)據(jù), 需要跳過這個無效數(shù)據(jù)
if (frameLength > maxFrameLength) {
// 跳過一個frameLength長度的數(shù)據(jù)
exceededFrameLength(in, frameLength);
return null;
}
// never overflows because it's less than maxFrameLength
int frameLengthInt = (int) frameLength;
// 表示目前可讀的數(shù)據(jù)還不夠一個幀, 那么直接返回
if (in.readableBytes() < frameLengthInt) {
return null;
}
// 比如一個即將要讀取的幀長度=10, 可是initialBytesToStrip = 12, 跳過的字節(jié)比要讀取的字節(jié)還大, 讀取的字節(jié)還不夠跳過的, 有問題 直接拋異常
if (initialBytesToStrip > frameLengthInt) {
failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
}
in.skipBytes(initialBytesToStrip);
// extract frame
// 讀取實際有意義的業(yè)務(wù)數(shù)據(jù)
int readerIndex = in.readerIndex();
int actualFrameLength = frameLengthInt - initialBytesToStrip;
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
in.readerIndex(readerIndex + actualFrameLength);
return frame;
}


總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java數(shù)據(jù)類型轉(zhuǎn)換實例解析
這篇文章主要介紹了Java數(shù)據(jù)類型轉(zhuǎn)換實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11
一篇文章帶你使用SpringBoot基于WebSocket的在線群聊實現(xiàn)
這篇文章主要介紹了一篇文章帶你使用SpringBoot基于WebSocket的在線群聊實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10
SpringBoot配置文件之properties和yml的使用
這篇文章主要介紹了SpringBoot配置文件之properties和yml的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
SpringBoot?把PageHelper分頁信息返回給前端的方法步驟
本文主要介紹了SpringBoot?把PageHelper分頁信息返回給前端的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-01-01
Java并發(fā)多線程編程之CountDownLatch的用法
這篇文章主要介紹了Java并發(fā)多線程編程之CountDownLatch的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06

