Nett分布式分隔符解碼器邏輯源碼剖析
前文傳送門(mén):Netty分布式行解碼器邏輯源碼解析
分隔符解碼器
基于分隔符解碼器DelimiterBasedFrameDecoder, 是按照指定分隔符進(jìn)行解碼的解碼器, 通過(guò)分隔符, 可以將二進(jìn)制流拆分成完整的數(shù)據(jù)包
同樣繼承了ByteToMessageDecoder并重寫(xiě)了decode方法
我們看其中的一個(gè)構(gòu)造方法
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) { this(maxFrameLength, true, delimiters); }
這里參數(shù)maxFrameLength代表最大長(zhǎng)度, delimiters是個(gè)可變參數(shù), 可以說(shuō)可以支持多個(gè)分隔符進(jìn)行解碼
我們進(jìn)入decode方法:
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { Object decoded = decode(ctx, in); if (decoded != null) { out.add(decoded); } }
這里同樣調(diào)用了其重載的decode方法并將解析好的數(shù)據(jù)添加到集合list中, 其父類(lèi)就可以遍歷out, 并將內(nèi)容傳播
我們跟到重載decode方法中
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { //行處理器(1) if (lineBasedDecoder != null) { return lineBasedDecoder.decode(ctx, buffer); } int minFrameLength = Integer.MAX_VALUE; ByteBuf minDelim = null; //找到最小長(zhǎng)度的分隔符(2) for (ByteBuf delim: delimiters) { //每個(gè)分隔符分隔的數(shù)據(jù)包長(zhǎng)度 int frameLength = indexOf(buffer, delim); if (frameLength >= 0 && frameLength < minFrameLength) { minFrameLength = frameLength; minDelim = delim; } } //解碼(3) //已經(jīng)找到分隔符 if (minDelim != null) { int minDelimLength = minDelim.capacity(); ByteBuf frame; //當(dāng)前分隔符否處于丟棄模式 if (discardingTooLongFrame) { //首先設(shè)置為非丟棄模式 discardingTooLongFrame = false; //丟棄 buffer.skipBytes(minFrameLength + minDelimLength); int tooLongFrameLength = this.tooLongFrameLength; this.tooLongFrameLength = 0; if (!failFast) { fail(tooLongFrameLength); } return null; } //處于非丟棄模式 //當(dāng)前找到的數(shù)據(jù)包, 大于允許的數(shù)據(jù)包 if (minFrameLength > maxFrameLength) { //當(dāng)前數(shù)據(jù)包+最小分隔符長(zhǎng)度 全部丟棄 buffer.skipBytes(minFrameLength + minDelimLength); //傳遞異常事件 fail(minFrameLength); return null; } //如果是正常的長(zhǎng)度 //解析出來(lái)的數(shù)據(jù)包是否忽略分隔符 if (stripDelimiter) { //如果不包含分隔符 //截取 frame = buffer.readRetainedSlice(minFrameLength); //跳過(guò)分隔符 buffer.skipBytes(minDelimLength); } else { //截取包含分隔符的長(zhǎng)度 frame = buffer.readRetainedSlice(minFrameLength + minDelimLength); } return frame; } else { //如果沒(méi)有找到分隔符 //非丟棄模式 if (!discardingTooLongFrame) { //可讀字節(jié)大于允許的解析出來(lái)的長(zhǎng)度 if (buffer.readableBytes() > maxFrameLength) { //將這個(gè)長(zhǎng)度記錄下 tooLongFrameLength = buffer.readableBytes(); //跳過(guò)這段長(zhǎng)度 buffer.skipBytes(buffer.readableBytes()); //標(biāo)記當(dāng)前處于丟棄狀態(tài) discardingTooLongFrame = true; if (failFast) { fail(tooLongFrameLength); } } } else { tooLongFrameLength += buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); } return null; } }
這里的方法也比較長(zhǎng), 這里也通過(guò)拆分進(jìn)行剖析
(1). 行處理器
(2). 找到最小長(zhǎng)度分隔符
(3). 解碼
首先看第一步行處理器:
if (lineBasedDecoder != null) { return lineBasedDecoder.decode(ctx, buffer); }
這里首先判斷成員變量lineBasedDecoder是否為空, 如果不為空則直接調(diào)用lineBasedDecoder的decode的方法進(jìn)行解碼, lineBasedDecoder實(shí)際上就是上一小節(jié)剖析的LineBasedFrameDecoder解碼器
這個(gè)成員變量, 會(huì)在分隔符是\n和\r\n的時(shí)候進(jìn)行初始化
我們看初始化該屬性的構(gòu)造方法
public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) { //代碼省略 //如果是基于行的分隔 if (isLineBased(delimiters) && !isSubclass()) { //初始化行處理器 lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast); this.delimiters = null; } else { //代碼省略 } //代碼省略 }
這里isLineBased(delimiters)會(huì)判斷是否是基于行的分隔, 跟到isLineBased方法中:
private static boolean isLineBased(final ByteBuf[] delimiters) { //分隔符長(zhǎng)度不為2 if (delimiters.length != 2) { return false; } //拿到第一個(gè)分隔符 ByteBuf a = delimiters[0]; //拿到第二個(gè)分隔符 ByteBuf b = delimiters[1]; if (a.capacity() < b.capacity()) { a = delimiters[1]; b = delimiters[0]; } //確保a是/r/n分隔符, 確保b是/n分隔符 return a.capacity() == 2 && b.capacity() == 1 && a.getByte(0) == '\r' && a.getByte(1) == '\n' && b.getByte(0) == '\n'; }
首先判斷長(zhǎng)度等于2, 直接返回false
然后拿到第一個(gè)分隔符a和第二個(gè)分隔符b, 然后判斷a的第一個(gè)分隔符是不是\r, a的第二個(gè)分隔符是不是\n, b的第一個(gè)分隔符是不是\n, 如果都為true, 則條件成立
我們回到decode方法中, 看步驟2, 找到最小長(zhǎng)度的分隔符:
這里最小長(zhǎng)度的分隔符, 意思就是從讀指針開(kāi)始, 找到最近的分隔符
for (ByteBuf delim: delimiters) { //每個(gè)分隔符分隔的數(shù)據(jù)包長(zhǎng)度 int frameLength = indexOf(buffer, delim); if (frameLength >= 0 && frameLength < minFrameLength) { minFrameLength = frameLength; minDelim = delim; } }
這里會(huì)遍歷所有的分隔符, 然后找到每個(gè)分隔符到讀指針到數(shù)據(jù)包長(zhǎng)度
然后通過(guò)if判斷, 找到長(zhǎng)度最小的數(shù)據(jù)包的長(zhǎng)度, 然后保存當(dāng)前數(shù)據(jù)包的的分隔符, 如下圖:
6-4-1
這里假設(shè)A和B同為分隔符, A分隔符到讀指針的長(zhǎng)度小于B分隔符到讀指針的長(zhǎng)度, 這里會(huì)找到最小的分隔符A, 分隔符的最小長(zhǎng)度, 就readIndex到A的長(zhǎng)度
我們繼續(xù)看第3步, 解碼:
if (minDelim != null) 表示已經(jīng)找到最小長(zhǎng)度分隔符, 我們繼續(xù)看if塊中的邏輯:
int minDelimLength = minDelim.capacity(); ByteBuf frame; if (discardingTooLongFrame) { discardingTooLongFrame = false; buffer.skipBytes(minFrameLength + minDelimLength); int tooLongFrameLength = this.tooLongFrameLength; this.tooLongFrameLength = 0; if (!failFast) { fail(tooLongFrameLength); } return null; } if (minFrameLength > maxFrameLength) { buffer.skipBytes(minFrameLength + minDelimLength); fail(minFrameLength); return null; } if (stripDelimiter) { frame = buffer.readRetainedSlice(minFrameLength); buffer.skipBytes(minDelimLength); } else { frame = buffer.readRetainedSlice(minFrameLength + minDelimLength); } return frame;
if (discardingTooLongFrame) 表示當(dāng)前是否處于非丟棄模式, 如果是丟棄模式, 則進(jìn)入if塊
因?yàn)榈谝粋€(gè)不是丟棄模式, 所以這里先分析if塊后面的邏輯
if (minFrameLength > maxFrameLength) 這里是判斷當(dāng)前找到的數(shù)據(jù)包長(zhǎng)度大于最大長(zhǎng)度, 這里的最大長(zhǎng)度使我們創(chuàng)建解碼器的時(shí)候設(shè)置的, 如果超過(guò)了最大長(zhǎng)度, 就通過(guò) buffer.skipBytes(minFrameLength + minDelimLength) 方式, 跳過(guò)數(shù)據(jù)包+分隔符的長(zhǎng)度, 也就是將這部分?jǐn)?shù)據(jù)進(jìn)行完全丟棄
繼續(xù)往下看, 如果長(zhǎng)度不大最大允許長(zhǎng)度, 則通過(guò) if (stripDelimiter) 判斷解析的出來(lái)的數(shù)據(jù)包是否包含分隔符, 如果不包含分隔符, 則截取數(shù)據(jù)包的長(zhǎng)度之后, 跳過(guò)分隔符
我們?cè)倩仡^看 if (discardingTooLongFrame) 中的if塊中的邏輯, 也就是丟棄模式:
首先將discardingTooLongFrame設(shè)置為false, 標(biāo)記非丟棄模式
然后通過(guò) buffer.skipBytes(minFrameLength + minDelimLength) 將數(shù)據(jù)包+分隔符長(zhǎng)度的字節(jié)數(shù)跳過(guò), 也就是進(jìn)行丟棄, 之后再進(jìn)行拋出異常
分析完成了找到分隔符之后的丟棄模式非丟棄模式的邏輯處理, 我們?cè)诜治鰶](méi)找到分隔符的邏輯處理, 也就是 if (minDelim != null) 中的else塊:
if (!discardingTooLongFrame) { if (buffer.readableBytes() > maxFrameLength) { tooLongFrameLength = buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); discardingTooLongFrame = true; if (failFast) { fail(tooLongFrameLength); } } } else { tooLongFrameLength += buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); } return null;
首先通過(guò) if (!discardingTooLongFrame) 判斷是否為非丟棄模式, 如果是, 則進(jìn)入if塊:
在if塊中, 首先通過(guò) if (buffer.readableBytes() > maxFrameLength) 判斷當(dāng)前可讀字節(jié)數(shù)是否大于最大允許的長(zhǎng)度, 如果大于最大允許的長(zhǎng)度, 則將可讀字節(jié)數(shù)設(shè)置到tooLongFrameLength的屬性中, 代表丟棄的字節(jié)數(shù)
然后通過(guò) buffer.skipBytes(buffer.readableBytes()) 將累計(jì)器中所有的可讀字節(jié)進(jìn)行丟棄
最后將discardingTooLongFrame設(shè)置為true, 也就是丟棄模式, 之后拋出異常
如果 if (!discardingTooLongFrame) 為false, 也就是當(dāng)前處于丟棄模式, 則追加tooLongFrameLength也就是丟棄的字節(jié)數(shù)的長(zhǎng)度, 并通過(guò) buffer.skipBytes(buffer.readableBytes()) 將所有的字節(jié)繼續(xù)進(jìn)行丟棄
以上就是分隔符解碼器的相關(guān)邏輯
章節(jié)總結(jié)
本章介紹了抽象解碼器ByteToMessageDecoder, 和其他幾個(gè)實(shí)現(xiàn)了ByteToMessageDecoder類(lèi)的解碼器, 這個(gè)幾個(gè)解碼器邏輯都比較簡(jiǎn)單, 同學(xué)們可以根據(jù)其中的思想剖析其他的比較復(fù)雜的解碼器, 或者根據(jù)其規(guī)則實(shí)現(xiàn)自己的自定義解碼器
更多關(guān)于Nett分布式分隔符解碼器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
三種SpringBoot中實(shí)現(xiàn)異步調(diào)用的方法總結(jié)
Spring Boot 提供了多種方式來(lái)實(shí)現(xiàn)異步任務(wù),這篇文章主要為大家介紹了常用的三種實(shí)現(xiàn)方式,文中的示例代碼講解詳細(xì),需要的可以參考一下2023-05-05Spring詳細(xì)講解事務(wù)失效的場(chǎng)景
實(shí)際項(xiàng)目開(kāi)發(fā)中,如果涉及到多張表操作時(shí),為了保證業(yè)務(wù)數(shù)據(jù)的一致性,大家一般都會(huì)采用事務(wù)機(jī)制,好多小伙伴可能只是簡(jiǎn)單了解一下,遇到事務(wù)失效的情況,便會(huì)無(wú)從下手,下面這篇文章主要給大家介紹了關(guān)于Spring事務(wù)失效場(chǎng)景的相關(guān)資料,需要的朋友可以參考下2022-07-07Java常見(jiàn)啟動(dòng)命令-jar、-server和-cp詳細(xì)比較
這篇文章主要給大家介紹了關(guān)于Java常見(jiàn)啟動(dòng)命令-jar、-server和-cp詳細(xì)比較的相關(guān)資料,該文總結(jié)了常歸的jar包的啟動(dòng)方式,并分析各種啟動(dòng)方式的區(qū)別,需要的朋友可以參考下2023-07-07SpringBoot2.2.X用Freemarker出現(xiàn)404的解決
這篇文章主要介紹了SpringBoot2.2.X用Freemarker出現(xiàn)404的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02JavaFX程序初次運(yùn)行創(chuàng)建數(shù)據(jù)庫(kù)并執(zhí)行建表SQL詳解
這篇文章主要介紹了JavaFX程序初次運(yùn)行創(chuàng)建數(shù)據(jù)庫(kù)并執(zhí)行建表SQL詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08Java使用Jsoup解析html網(wǎng)頁(yè)的實(shí)現(xiàn)步驟
Jsoup是一個(gè)用于解析HTML文檔的Java庫(kù),本文主要介紹了Java使用Jsoup解析html網(wǎng)頁(yè)的實(shí)現(xiàn)步驟,可以提取文本、鏈接、圖片等,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02Java Code Cache滿(mǎn)導(dǎo)致應(yīng)用性能降低問(wèn)題解決
這篇文章主要介紹了Java Code Cache滿(mǎn)導(dǎo)致應(yīng)用性能降低問(wèn)題解決,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08