數據包編解碼過程中主要的工作就是:在編碼過程中進行序列化,在解碼過程中從Byte流中分離出數據包然後反序列化。在MessageToByteEncoder中,已經解決了序列化之後的問題,ByteToMessageDecoder中已經部分第解決了從Byte流中分離出數據包的問題。實現具體的數據包編解碼,只需要實現MessageToByteEncoder的encode和ByteToMessageDecoder的decode方法即可。
爲了方便開發者使用Netty,在io.netty.handler.codec包中已經實現了一些開箱即用的編解碼ChannelHandler,這些Handler包括:
- FixedLengthFrameDecoder : 固定長度的數據包解碼。
- LengthFieldPrepender, LengthFieldBasedFrameDecoder: 帶有長度字段的數據包編解碼。
- LineBasedFrameDecoder: 以行字符串爲一個數據包解碼。
- StringEncoder, StringDecoder: 字符集轉換器。
- Base64Encoder, Base64Decoder: Base64編碼轉換器。
- ProtobufEncoder, ProtobufDecoder: protoBuf序列化格式數據包的編解碼。
- ZlibEncoder,ZlibDecoder: zlib壓縮格式數據包的編解碼。
- 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實現更加健壯的,安全的同時兼顧性能的數據包,關於這個話題將在下一章詳細講解。