netty源碼解析(4.0)-19 ChannelHandler: codec--常用編解碼實現

  數據包編解碼過程中主要的工作就是:在編碼過程中進行序列化,在解碼過程中從Byte流中分離出數據包然後反序列化。在MessageToByteEncoder中,已經解決了序列化之後的問題,ByteToMessageDecoder中已經部分第解決了從Byte流中分離出數據包的問題。實現具體的數據包編解碼,只需要實現MessageToByteEncoder的encode和ByteToMessageDecoder的decode方法即可。

  爲了方便開發者使用Netty,在io.netty.handler.codec包中已經實現了一些開箱即用的編解碼ChannelHandler,這些Handler包括:

  1. FixedLengthFrameDecoder : 固定長度的數據包解碼。
  2. LengthFieldPrepender, LengthFieldBasedFrameDecoder: 帶有長度字段的數據包編解碼。
  3. LineBasedFrameDecoder: 以行字符串爲一個數據包解碼。
  4. StringEncoder, StringDecoder: 字符集轉換器。
  5. Base64Encoder, Base64Decoder: Base64編碼轉換器。
  6. ProtobufEncoder, ProtobufDecoder: protoBuf序列化格式數據包的編解碼。
  7. ZlibEncoder,ZlibDecoder: zlib壓縮格式數據包的編解碼。
  8. SnappyFramedEncoder,SnappyFramedDecoder: Snappy壓縮格式數據包的編解碼。

  接下來讓我們來分析幾個關鍵ChannelHandler的實現代碼。

 

帶有長度字段的數據解碼: LengthFieldBasedFrameDecoder

  之所以先從LengthFieldBasedFrameDecoder開始,是因爲這個類的實現非常典型,它向我們展示瞭解碼二進制數據包的一些常用方法:

  • 從Byte流中反序列化出一個整數。
  • 利用整數表示一個數據包的長度。
  • 使用偏移量得到數據包的開始位置。
  • 使用數據包的開始位置和長度從Byte流中提取數據包。

  這個類解碼的數據包可能有三中格式:

   | length | content | : 其中length是長度字段,它表示content的長度。但是這樣格式無法明確地找到數據包的開始位置,需要一個表示開始位置的字段。

   | header | length | conent | : 通過header字段確定數據包的開始位置。

   | header | length | header1 | content |: header2是一個固定長度的字段,它可能包含一些子字段。

  有一些必要的屬性用來輔助解碼數據包:

  byteOrder: 反序列化整數使用的字節序。

  lengthFieldOffset:  長度字段的偏移量,也是header的長度。

  lengthFieldLength: 長度字段的長度。長度字段是一個整數,它的長度可能是: 1, 2, 3, 4, 8。

  lengthAdjustment:  header1的長度。

  initialBytesToStrip: 從Byte流中提取數據包時去掉的數據長度。

  通過覆蓋decode方法從Byte流中提取數據包:

複製代碼

1     @Override
2     protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
3         Object decoded = decode(ctx, in);
4         if (decoded != null) {
5             out.add(decoded);
6         }
7     }

複製代碼

  代碼比較簡單,調用內部的decode方法完成數據包的提取,乾貨都在這個內部方法中。

複製代碼

 1     protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
 2         if (discardingTooLongFrame) {
 3             discardingTooLongFrame(in);
 4         }
 5 
 6         if (in.readableBytes() < lengthFieldEndOffset) {
 7             return null;
 8         }
 9 
10         int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
11         long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
12 
13         if (frameLength < 0) {
14             failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
15         }
16 
17         frameLength += lengthAdjustment + lengthFieldEndOffset;
18 
19         if (frameLength < lengthFieldEndOffset) {
20             failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
21         }
22 
23         if (frameLength > maxFrameLength) {
24             exceededFrameLength(in, frameLength);
25             return null;
26         }
27 
28         // never overflows because it's less than maxFrameLength
29         int frameLengthInt = (int) frameLength;
30         if (in.readableBytes() < frameLengthInt) {
31             return null;
32         }
33 
34         if (initialBytesToStrip > frameLengthInt) {
35             failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
36         }
37         in.skipBytes(initialBytesToStrip);
38 
39         // extract frame
40         int readerIndex = in.readerIndex();
41         int actualFrameLength = frameLengthInt - initialBytesToStrip;
42         ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
43         in.readerIndex(readerIndex + actualFrameLength);
44         return frame;
45     }

複製代碼

  調用extractFrame(42行)從Byte流中提取數據包之前的關鍵環節有:

  • 找到數據包的開始位置:  10行。  
  • 確定數據包的長度: 11行,調用getUnadjustedFrameLength方法反序列化到的length字段值。此時frameLength值是content的長度,還不是真正的包長度。17行,content長度加上header1的長度(lengthAjustment)再加上header和length的長度(lengthFieldEndOffset),得到的結果纔是真正的包長度。 41行,最後減去需要丟掉的那部分數據的長度(initialBytesStrip),得到期望的數據包長度。
  • 處理in中數據不足一個數據包的情況: 6-7行,30-31行,返回null。
  • 清理掉in中超過最大數據包長度限制的數據: 23-24行如果in中的數據大於frameLength丟掉這個數據包,否則丟掉in中現有的所有數據,記錄下還需要丟棄的數據長度,下次在 2-3行丟掉剩下長度的數據。 
  • 處理包長度錯誤的情況: 13-14行,19-20行丟掉header和length。34-35行丟掉frameLength長度的數據。 

  最後得到得到數據包的開始位置和長度之和從in緩衝區中提取出一個完整數據包,並修改in的讀位置, 43-44行。  

 

  getUnadjustedFrameLength中反序列環length的值是計算數據包長度的關鍵,這個方法的實現如下:

複製代碼

 1     protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
 2         buf = buf.order(order);
 3         long frameLength;
 4         switch (length) {
 5         case 1:
 6             frameLength = buf.getUnsignedByte(offset);
 7             break;
 8         case 2:
 9             frameLength = buf.getUnsignedShort(offset);
10             break;
11         case 3:
12             frameLength = buf.getUnsignedMedium(offset);
13             break;
14         case 4:
15             frameLength = buf.getUnsignedInt(offset);
16             break;
17         case 8:
18             frameLength = buf.getLong(offset);
19             break;
20         default:
21             throw new DecoderException(
22                     "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
23         }
24         return frameLength;
25     }

複製代碼

  2行設置ByteBuf反序列化整數時使用的字節序,默認BIG_ENDIAN。這個值可以在調用構造方法時設置。

  4-24行,根據length字段的不同長度,使用ByteBuf的不同方法反序列化length的值。length字段的長度只能是: 1,2,3,4,8之一。

  

  以上是LengthFieldBasedFrameDecoder實現的核心代碼的分析。這個類把一個比較關鍵的問題留給子類實現,就是如何處理header或header1字段的處理, header1可以沒有,但一定要有header。他們內部可以有比較複雜的結構,而且長度是約定好的,不會變。這個就給子類留下了比較大的擴展空間。通過擴展這個類,我們可以實現任意格式數據包的提取功能。

 

帶有長度字段的數據包編碼: LengthFieldPrepender

  這個類實現的功能和LengthFieldBasedFrameDecoder相反,也比它要簡單很多,只是簡單地在已經序列化好的數據前面加上長度字段。

複製代碼

 1     @Override
 2     protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
 3         int length = msg.readableBytes() + lengthAdjustment;
 4         if (lengthIncludesLengthFieldLength) {
 5             length += lengthFieldLength;
 6         }
 7 
 8         if (length < 0) {
 9             throw new IllegalArgumentException(
10                     "Adjusted frame length (" + length + ") is less than zero");
11         }
12 
13         switch (lengthFieldLength) {
14         case 1:
15             if (length >= 256) {
16                 throw new IllegalArgumentException(
17                         "length does not fit into a byte: " + length);
18             }
19             out.writeByte((byte) length);
20             break;
21         case 2:
22             if (length >= 65536) {
23                 throw new IllegalArgumentException(
24                         "length does not fit into a short integer: " + length);
25             }
26             out.writeShort((short) length);
27             break;
28         case 3:
29             if (length >= 16777216) {
30                 throw new IllegalArgumentException(
31                         "length does not fit into a medium integer: " + length);
32             }
33             out.writeMedium(length);
34             break;
35         case 4:
36             out.writeInt(length);
37             break;
38         case 8:
39             out.writeLong(length);
40             break;
41         default:
42             throw new Error("should not reach here");
43         }
44 
45         out.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
46     }

複製代碼

  3-11行,計算並檢查長度字段的值。

    13-43行,在數據前面加上長度字段。

  45行,把數據追加到長度長度字段之後。

 

行數據包和固定長度數據包的提取

  LineBasedFrameDecoder: 用於從字符串流中按行提取數據包。用"\n"或"\r\n"作爲前一個數據包的結束標誌和下一個數據包的開始標誌,也是根據這個標誌算出數據包的長度。

  FixedLengthFrameDecoder: 用於從Byte流中安固定長度提取數據包。數據包沒有明確的開始標誌或結束標誌,只是簡單地根據約定的長度提取數據包。

  這個中數據包提取方式對數據源可靠性要求較高,實際應用中要多加小心。

  

字符集轉換

  StringEncoder, StringDecoder這兩個類用於把字符串從一種字符集轉換成另外一種字符集,不涉及數據包的提取。如:GBK和UTF-8之間的轉換。

 

Base64編解碼

  Base64Encoder, Base64Decoder這個兩個類用於base64的編解碼,不涉及數據包的提取。

  

ProtoBuf編解碼

  ProtobufEncoder, ProtobufDecoder用ProtoBuf格式數據包的編解碼,不涉及數據包的的提取。

 

壓縮和解壓縮

  ZlibEncoder,ZlibDecoder: 對zlib和gzip壓縮和解壓縮算法的支持,不涉及數據包的提取。

  SnappyFramedEncoder,SnappyFramedDecoder: 最Snappy壓縮和解壓算法的支持,不涉及數據包的提取。

 

 本章小結

  本章分析了分析了Netty使用編解碼框架實現特定用途的編解碼工具,其中最重要的是LengthFieldPrepender, LengthFieldBasedFrameDecoder類,這兩個類展示了對任意二進制數據包打包,和從Byte流中提取二進制數據包的常用方法,理解了這兩個類的實現,就能針對任意類型數據包實現自己的編解碼Handler。其他的類比較簡單,大部分不涉及數據包的提取,只涉及到一些常用的算法和編碼格式,Netty在這裏只是提供了一些開箱即用的工具。

  前講過LengthFieldBasedFrameDecoder把對header字段的處理留給子類,這意味着子類可以通過設計自己的header實現更加健壯的,安全的同時兼顧性能的數據包,關於這個話題將在下一章詳細講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章