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