詳解netty中的frame解碼器
簡介
netty中的數(shù)據(jù)是通過ByteBuf來進(jìn)行傳輸?shù)?,一個(gè)ByteBuf中可能包含多個(gè)有意義的數(shù)據(jù),這些數(shù)據(jù)可以被稱作frame,也就是說一個(gè)ByteBuf中可以包含多個(gè)Frame。
對于消息的接收方來說,接收到了ByteBuf,還需要從ByteBuf中解析出有用而數(shù)據(jù),那就需要將ByteBuf中的frame進(jìn)行拆分和解析。
一般來說不同的frame之間會(huì)有有些特定的分隔符,我們可以通過這些分隔符來區(qū)分frame,從而實(shí)現(xiàn)對數(shù)據(jù)的解析。
netty為我們提供了一些合適的frame解碼器,通過使用這些frame解碼器可以有效的簡化我們的工作。下圖是netty中常見的幾個(gè)frame解碼器:
接下來我們來詳細(xì)介紹一下上面幾個(gè)frame解碼器的使用。
LineBasedFrameDecoder
LineBasedFrameDecoder從名字上看就是按行來進(jìn)行frame的區(qū)分。根據(jù)操作系統(tǒng)的不同,換行可以有兩種換行符,分別是 “\n” 和 “\r\n” 。
LineBasedFrameDecoder的基本原理就是從ByteBuf中讀取對應(yīng)的字符來和"\n" 跟 “\r\n”,可以了可以準(zhǔn)確的進(jìn)行字符的比較,這些frameDecoder對字符的編碼也會(huì)有一定的要求,一般來說是需要UTF-8編碼。因?yàn)樵谶@樣的編碼中,“\n"和”\r"是以一個(gè)byte出現(xiàn)的,并且不會(huì)用在其他的組合編碼中,所以用"\n"和"\r"來進(jìn)行判斷是非常安全的。
LineBasedFrameDecoder中有幾個(gè)比較重要的屬性,一個(gè)是maxLength的屬性,用來檢測接收到的消息長度,如果超出了長度限制,則會(huì)拋出TooLongFrameException異常。
還有一個(gè)stripDelimiter屬性,用來判斷是否需要將delimiter過濾掉。
還有一個(gè)是failFast,如果該值為true,那么不管frame是否讀取完成,只要frame的長度超出了maxFrameLength,就會(huì)拋出TooLongFrameException。如果該值為false,那么TooLongFrameException會(huì)在整個(gè)frame完全讀取之后再拋出。
LineBasedFrameDecoder的核心邏輯是先找到行的分隔符的位置,然后根據(jù)這個(gè)位置讀取到對應(yīng)的frame信息,這里來看一下找到行分隔符的findEndOfLine方法:
private int findEndOfLine(final ByteBuf buffer) { int totalLength = buffer.readableBytes(); int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF); if (i >= 0) { offset = 0; if (i > 0 && buffer.getByte(i - 1) == '\r') { i--; } } else { offset = totalLength; } return i; }
這里使用了一個(gè)ByteBuf的forEachByte對ByteBuf進(jìn)行遍歷。我們要找的字符是:ByteProcessor.FIND_LF。
最后LineBasedFrameDecoder解碼之后的對象還是一個(gè)ByteBuf。
DelimiterBasedFrameDecoder
上面講的LineBasedFrameDecoder只對行分隔符有效,如果我們的frame是以其他的分隔符來分割的話LineBasedFrameDecoder就用不了了,所以netty提供了一個(gè)更加通用的DelimiterBasedFrameDecoder,這個(gè)frameDecoder可以自定義delimiter:
public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder { public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) { this(maxFrameLength, true, delimiter); }
傳入的delimiter是一個(gè)ByteBuf,所以delimiter可能不止一個(gè)字符。
為了解決這個(gè)問題在DelimiterBasedFrameDecoder中定義了一個(gè)ByteBuf的數(shù)組:
private final ByteBuf[] delimiters; delimiters= delimiter.readableBytes();
這個(gè)delimiters是通過調(diào)用delimiter的readableBytes得到的。
DelimiterBasedFrameDecoder的邏輯和LineBasedFrameDecoder差不多,都是通過對比bufer中的字符來對bufer中的數(shù)據(jù)進(jìn)行截取,但是DelimiterBasedFrameDecoder可以接受多個(gè)delimiters,所以它的用處會(huì)根據(jù)廣泛。
FixedLengthFrameDecoder
除了進(jìn)行ByteBuf中字符比較來進(jìn)行frame拆分之外,還有一些其他常見的frame拆分的方法,比如根據(jù)特定的長度來區(qū)分,netty提供了一種這樣的decoder叫做FixedLengthFrameDecoder。
public class FixedLengthFrameDecoder extends ByteToMessageDecoder
FixedLengthFrameDecoder也是繼承自ByteToMessageDecoder,它的定義很簡單,可以傳入一個(gè)frame的長度:
public FixedLengthFrameDecoder(int frameLength) { checkPositive(frameLength, "frameLength"); this.frameLength = frameLength; }
然后調(diào)用ByteBuf的readRetainedSlice方法來讀取固定長度的數(shù)據(jù):
in.readRetainedSlice(frameLength)
最后將讀取到的數(shù)據(jù)返回。
LengthFieldBasedFrameDecoder
還有一些frame中包含了特定的長度字段,這個(gè)長度字段表示ByteBuf中有多少可讀的數(shù)據(jù),這樣的frame叫做LengthFieldBasedFrame。
netty中也提供了一個(gè)對應(yīng)的處理decoder:
public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder
讀取的邏輯很簡單,首先讀取長度,然后再根據(jù)長度再讀取數(shù)據(jù)。為了實(shí)現(xiàn)這個(gè)邏輯,LengthFieldBasedFrameDecoder提供了4個(gè)字段,分別是 lengthFieldOffset,lengthFieldLength,lengthAdjustment和initialBytesToStrip。
lengthFieldOffset指定了長度字段的開始位置,lengthFieldLength定義的是長度字段的長度,lengthAdjustment是對lengthFieldLength進(jìn)行調(diào)整,initialBytesToStrip表示是否需要去掉長度字段。
聽起來好像不太好理解,我們舉幾個(gè)例子,首先是最簡單的:
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) +--------+----------------+ +--------+----------------+ | Length | Actual Content |----->| Length | Actual Content | | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | +--------+----------------+ +--------+----------------+
要編碼的消息有個(gè)長度字段,長度字段后面就是真實(shí)的數(shù)據(jù),0x000C是一個(gè)十六進(jìn)制,表示的數(shù)據(jù)是12,也就是"HELLO, WORLD" 中字符串的長度。
這里4個(gè)屬性的值是:
lengthFieldOffset = 0 lengthFieldLength = 2 lengthAdjustment = 0 initialBytesToStrip = 0
表示的是長度字段從0開始,并且長度字段占有兩個(gè)字節(jié),長度不需要調(diào)整,也不需要對字段進(jìn)行調(diào)整。
再來看一個(gè)比較復(fù)雜的例子,在這個(gè)例子中4個(gè)屬性值如下:
lengthFieldOffset = 1 lengthFieldLength = 2 lengthAdjustment = 1 initialBytesToStrip = 3
對應(yīng)的編碼數(shù)據(jù)如下所示:
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) +------+--------+------+----------------+ +------+----------------+ | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | +------+--------+------+----------------+ +------+----------------+
上面的例子中長度字段是從第1個(gè)字節(jié)開始的(第0個(gè)字節(jié)是HDR1),長度字段占有2個(gè)字節(jié),長度再調(diào)整一個(gè)字節(jié),最終數(shù)據(jù)的開始位置就是1+2+1=4,然后再截取前3個(gè)字節(jié)的數(shù)據(jù),得到了最后的結(jié)果。
總結(jié)
netty提供的這幾個(gè)基于字符集的frame decoder基本上能夠滿足我們?nèi)粘5墓ぷ餍枨罅?。?dāng)然,如果你傳輸?shù)氖且恍└訌?fù)雜的對象,那么可以考慮自定義編碼和解碼器。自定義的邏輯步驟和上面我們講解的保持一致就行了。
到此這篇關(guān)于netty中的frame解碼器的文章就介紹到這了,更多相關(guān)netty解碼器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項(xiàng)目配置logback日志系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了springboot項(xiàng)目配置logback日志系統(tǒng)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04java微信開發(fā)API第三步 微信獲取以及保存接口調(diào)用憑證
這篇文章主要為大家詳細(xì)介紹了java微信開發(fā)API第二步,微信獲取以及保存接口調(diào)用憑證,感興趣的小伙伴們可以參考一下2016-06-06mybatis那些約定的配置你真的都了解嗎(經(jīng)驗(yàn)總結(jié))
mybatsi中Mapper和xml文件之間有很多約定俗稱的規(guī)則,比如名稱匹配,包掃描,別名等,這些規(guī)則是什么。如果想更加靈活,該如何配置呢?今天就給大家講一下如何配置mybatsi的xml文件2021-06-06Apache DolphinScheduler實(shí)現(xiàn)自動(dòng)化打包單機(jī)/集群部署詳解
這篇文章主要為大家介紹了Apache DolphinScheduler實(shí)現(xiàn)自動(dòng)化打包單機(jī)/集群部署詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09