Netty原始碼分析 (十)----- 拆包器之LineBasedFrameDecoder
Netty 自帶多個粘包拆包解碼器。今天介紹 LineBasedFrameDecoder,換行符解碼器。
行拆包器
下面,以一個具體的例子來看看業netty自帶的拆包器是如何來拆包的
這個類叫做 LineBasedFrameDecoder
,基於行分隔符的拆包器,TA可以同時處理 \n
以及\r\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); } }
netty 中自帶的拆包器都是如上這種模板,我們來看看decode(ctx, in);
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { int eol = findEndOfLine(buffer); int length; int length; if (!this.discarding) { if (eol >= 0) { length = eol - buffer.readerIndex(); int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1; if (length > this.maxLength) { buffer.readerIndex(eol + delimLength); this.fail(ctx, length); return null; } else { ByteBuf frame; if (this.stripDelimiter) { frame = buffer.readRetainedSlice(length); buffer.skipBytes(delimLength); } else { frame = buffer.readRetainedSlice(length + delimLength); } return frame; } } else { length = buffer.readableBytes(); if (length > this.maxLength) { this.discardedBytes = length; buffer.readerIndex(buffer.writerIndex()); this.discarding = true; if (this.failFast) { this.fail(ctx, "over " + this.discardedBytes); } } return null; } } else { if (eol >= 0) { length = this.discardedBytes + eol - buffer.readerIndex(); length = buffer.getByte(eol) == '\r' ? 2 : 1; buffer.readerIndex(eol + length); this.discardedBytes = 0; this.discarding = false; if (!this.failFast) { this.fail(ctx, length); } } else { this.discardedBytes += buffer.readableBytes(); buffer.readerIndex(buffer.writerIndex()); } return null; } } ByteProcessor FIND_LF = new IndexOfProcessor((byte) '\n'); private static int findEndOfLine(ByteBuf buffer) { int i = buffer.forEachByte(ByteProcessor.FIND_LF); if (i > 0 && buffer.getByte(i - 1) == '\r') { --i; } return i; }
找到換行符位置
final int eol = findEndOfLine(buffer); private static int findEndOfLine(final ByteBuf buffer) { int i = buffer.forEachByte(ByteProcessor.FIND_LF); if (i > 0 && buffer.getByte(i - 1) == '\r') { i--; } return i; } ByteProcessor FIND_LF = new IndexOfProcessor((byte) '\n');
for迴圈遍歷,找到第一個 \n
的位置,如果\n
前面的字元為\r
,那就返回\r
的位置
非discarding模式的處理
接下來,netty會判斷,當前拆包是否屬於丟棄模式,用一個成員變數來標識
private boolean discarding;
第一次拆包不在discarding模式
非discarding模式下找到行分隔符的處理
// 1.計算分隔符和包長度 final ByteBuf frame; final int length = eol - buffer.readerIndex(); final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1; // 丟棄異常資料 if (length > maxLength) { buffer.readerIndex(eol + delimLength); fail(ctx, length); return null; } // 取包的時候是否包括分隔符 if (stripDelimiter) { frame = buffer.readRetainedSlice(length); buffer.skipBytes(delimLength); } else { frame = buffer.readRetainedSlice(length + delimLength); } return frame;
1.首先,新建一個幀,計算一下當前包的長度和分隔符的長度(因為有兩種分隔符)
2.然後判斷一下需要拆包的長度是否大於該拆包器允許的最大長度(maxLength
),這個引數在建構函式中被傳遞進來,如超出允許的最大長度,就將這段資料拋棄,返回null
3.最後,將一個完整的資料包取出,如果構造本解包器的時候指定 stripDelimiter
為false,即解析出來的包包含分隔符,預設為不包含分隔符
非discarding模式下未找到分隔符的處理
沒有找到對應的行分隔符,說明位元組容器沒有足夠的資料拼接成一個完整的業務資料包,進入如下流程處理
final int length = buffer.readableBytes(); if (length > maxLength) { discardedBytes = length; buffer.readerIndex(buffer.writerIndex()); discarding = true; if (failFast) { fail(ctx, "over " + discardedBytes); } } return null;
首先取得當前位元組容器的可讀位元組個數,接著,判斷一下是否已經超過可允許的最大長度,如果沒有超過,直接返回null,位元組容器中的資料沒有任何改變,否則,就需要進入丟棄模式
使用一個成員變數 discardedBytes
來表示已經丟棄了多少資料,然後將位元組容器的讀指標移到寫指標,意味著丟棄這一部分資料,設定成員變數discarding
為true表示當前處於丟棄模式。如果設定了failFast
,那麼直接丟擲異常,預設情況下failFast
為false,即安靜得丟棄資料
discarding模式
如果解包的時候處在discarding模式,也會有兩種情況發生
discarding模式下找到行分隔符
在discarding模式下,如果找到分隔符,那可以將分隔符之前的都丟棄掉
final int length = discardedBytes + eol - buffer.readerIndex(); final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1; buffer.readerIndex(eol + delimLength); discardedBytes = 0; discarding = false; if (!failFast) { fail(ctx, length); }
計算出分隔符的長度之後,直接把分隔符之前的資料全部丟棄,當然丟棄的字元也包括分隔符,經過這麼一次丟棄,後面就有可能是正常的資料包,下一次解包的時候就會進入正常的解包流程
discarding模式下未找到行分隔符
這種情況比較簡單,因為當前還在丟棄模式,沒有找到行分隔符意味著當前一個完整的資料包還沒丟棄完,當前讀取的資料是丟棄的一部分,所以直接丟棄
discardedBytes += buffer.readableBytes(); buffer.readerIndex(buffer.writerIndex());
特定分隔符拆包
這個類叫做 DelimiterBasedFrameDecoder
,可以傳遞給TA一個分隔符列表,資料包會按照分隔符列表進行拆分,讀者可以完全根據行拆包器的思路去分析這個DelimiterBasedFrameDecoder
&n