簡介
上篇文章我們講到了netty中怎麼自定義編碼和解碼器,但是自定義實現起來還是挺複雜的,一般沒有特殊必要的情況下,大家都希望越簡單越好,其難點就是找到ByteBuf中的分割點,將ByteBuf分割成為一個個的可以處理的單元。今天本文講講netty中自帶的分割處理機制。
Frame detection
在上一章,我們提到了需要有一種手段來區分ByteBuf中不同的資料,也就是說找到ByteBuf中不同資料的分割點。如果首先將ByteBuf分割成一個個的獨立的ByteBuf,再對獨立的ByteBuf進行處理就會簡單很多。
netty中提供了4個分割點的編碼器,我們可以稱之為Frame detection,他們分別是DelimiterBasedFrameDecoder, FixedLengthFrameDecoder, LengthFieldBasedFrameDecoder, 和 LineBasedFrameDecoder。
這幾個類都是ByteToMessageDecoder的子類,接下來我們一一進行介紹。
DelimiterBasedFrameDecoder
首先是DelimiterBasedFrameDecoder,看名字就知道這個是根據delimiter對bytebuf進行分割的解碼器。什麼是delimiter呢?
netty中有一個Delimiters類,專門定義分割的字元,主要有兩個delimiter,分別是nulDelimiter和lineDelimiter:
public static ByteBuf[] nulDelimiter() {
return new ByteBuf[] {
Unpooled.wrappedBuffer(new byte[] { 0 }) };
}
public static ByteBuf[] lineDelimiter() {
return new ByteBuf[] {
Unpooled.wrappedBuffer(new byte[] { '\r', '\n' }),
Unpooled.wrappedBuffer(new byte[] { '\n' }),
};
}
nullDelimiter用來處理0x00,主要用來處理Flash XML socket或者其他的類似的協議。
lineDelimiter用來處理回車和換行符,主要用來文字檔案的處理中。
對於DelimiterBasedFrameDecoder來說,如果有多個delimiter的話,會選擇將ByteBuf分割最短的那個,舉個例子,如果我們使用DelimiterBasedFrameDecoder(Delimiters.lineDelimiter()) ,因為lineDelimiter中實際上有兩個分割方式,回車+換行或者換行,如果遇到下面的情況:
+--------------+
| ABC\nDEF\r\n |
+--------------+
DelimiterBasedFrameDecoder會選擇最短的分割結果,也就說將上面的內容分割成為:
+-----+-----+
| ABC | DEF |
+-----+-----+
而不是
+----------+
| ABC\nDEF |
+----------+
FixedLengthFrameDecoder
這個類會將ByteBuf分成固定的長度,比如收到了下面的4塊byte資訊:
+---+----+------+----+
| A | BC | DEFG | HI |
+---+----+------+----+
如果使用一個FixedLengthFrameDecoder(3) ,則會將上面的ByteBuf分成下面的幾個部分:
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
LengthFieldBasedFrameDecoder
這個類就更加靈活一點,可以根據資料中的length欄位取出後續的byte陣列。LengthFieldBasedFrameDecoder非常靈活,它有4個屬性來控制他們分別是lengthFieldOffset、lengthFieldLength、lengthAdjustment和initialBytesToStrip。
lengthFieldOffset是長度欄位的起始位置,lengthFieldLength是長度欄位本身的長度,lengthAdjustment是對目標資料長度進行調整,initialBytesToStrip是解密過程中需要刪除的byte數目。理解不了?沒關係,我們來舉幾個例子。
首先看一個最簡單的:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
上面的設定表示,length是從第0位開始的,長度是2個位元組。其中Ox00C=12, 這也是“HELLO, WORLD” 的長度。
如果不想要Length欄位,可以通過設定initialBytesToStrip把length刪除:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2 (= length 欄位的長度)
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
lengthAdjustment是對Length欄位的值進行調整,因為在有些情況下Length欄位可能包含了整條資料的長度,也就是Length+內容,所以需要在解析的時候進行調整,比如下面的例子,真實長度其實是0x0C,但是傳入的卻是0x0E,所以需要減去Length欄位的長度2,也就是將lengthAdjustment設定為-2。
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2 (= Length欄位的長度)
initialBytesToStrip = 0
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
LineBasedFrameDecoder
LineBasedFrameDecoder專門處理文字檔案中的一行結束。也就是 "\n" 和 "\r\n",他和DelimiterBasedFrameDecoder很類似,但是DelimiterBasedFrameDecoder更加通用。
總結
有了上面4個Frame detection裝置之後,就可以在pipline中首先新增這些Frame detection,然後再新增自定義的handler,這樣在自定義的handler中就不用考慮讀取ByteBuf的長度問題了。
比如在StringDecoder中,如果已經使用了 LineBasedFrameDecoder , 那麼在decode方法中可以假設傳入的ByteBuf就是一行字串,那麼可以直接這樣使用:
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
out.add(msg.toString(charset));
}
是不是很簡單?
本文已收錄於 http://www.flydean.com/15-netty-buildin-frame-detection/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!