簡介
netty之所以強大,是因為它內建了很多非常有用的編碼解碼器,通過使用這些編碼解碼器可以很方便的搭建出非常強大的應用程式,今天給大家講講netty中最基本的內建編碼解碼器。
netty中的內建編碼器
在對netty的包進行引入的時候,我們可以看到netty有很多以netty-codec開頭的artifactId,統計一下,有這麼多個:
netty-codec
netty-codec-http
netty-codec-http2
netty-codec-memcache
netty-codec-redis
netty-codec-socks
netty-codec-stomp
netty-codec-mqtt
netty-codec-haproxy
netty-codec-dns
總共10個codec包,其中netty-codec是最基礎的一個,其他的9個是對不同的協議包進行的擴充套件和適配,可以看到netty支援常用的和流行的協議格式,非常的強大。因為codec的內容非常多,要講解他們也不是很容易,本文將會以netty-codec做一個例子,講解其中最基本的也是最通用的編碼解碼器。
使用codec要注意的問題
雖然netty提供了很方便的codec編碼解碼器,但是正如我們在前一篇文章中提到的,有些codec是需要和Frame detection一起配合使用的,先使用Frame detection將ByteBuf拆分成一個個代表真實資料的ByteBuf,再交由netty內建的codec或者自定義的codec進行處理,這樣才能起到應有的效果。
netty內建的基本codec
netty中基本的codec有base64、bytes、compression、json、marshalling、protobuf、serialization、string和xml這幾種。
下面將會一一進行講解。
base64
這個codec是負責ByteBuf和base64過後的ByteBuf之間的轉換。雖然都是從ByteBuf到ByteBuf,但是其中的內容發生了變化。
有兩個關鍵的類,分別是Base64Encoder和Base64Decoder。因為Base64Decoder是一個MessageToMessageDecoder,所以需要使用一個DelimiterBasedFrameDecoder提前進行處理,常用的例子如下:
ChannelPipeline pipeline = ...;
// Decoders
pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(80, Delimiters.nulDelimiter()));
pipeline.addLast("base64Decoder", new Base64Decoder());
// Encoder
pipeline.addLast("base64Encoder", new Base64Encoder());
bytes
bytes是將bytes陣列和ByteBuf之間進行轉換,同樣的在decode之前,也需要使用FrameDecoder,通常的使用方式如下:
ChannelPipeline pipeline = ...;
// Decoders
pipeline.addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
pipeline.addLast("bytesDecoder",
new ByteArrayDecoder());
// Encoder
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("bytesEncoder", new ByteArrayEncoder());
compression
compression這個包的內容就比較豐富了,主要是對資料的壓縮和解壓縮服務。其支援的演算法如下:
brotli
Bzip2
FastLZ
JdkZlib
Lz4
Lzf
Snappy
Zlib
Zstandard
compression對於大資料量的傳輸特別有幫助,通過壓縮可以節省傳輸的資料量,從而提高傳輸速度。
但是壓縮是使用特定的演算法來計算的,所以它是一個高CPU的操作,我們在使用的時候需要兼顧網路速度和CPU效能,並從中得到平衡。
json
json這個包裡面只有一個JsonObjectDecoder類,主要負責將Byte流的JSON物件或者陣列轉換成JSON物件和陣列。
JsonObjectDecoder直接就是一個ByteToMessageDecoder的子類,所以它不需要FrameDecoder,它是根據括號的匹配來判斷Byte陣列的起始位置,從而區分哪些Byte資料是屬於同一個Json物件或者陣列。
我們如果希望使用JSON來傳輸資料的話,這個類就非常有用了。
marshalling
Marshalling的全稱叫做JBoss Marshalling,它是JBoss出品的一個物件序列化的方式,但是JBoss Marshalling的最新API還是在2011-04-27,已經有10年沒更新了,是不是已經被廢棄了?
所以這裡我們不詳細介紹這個序列化的內容,感興趣的小夥伴可以自行探索。
protobuf
protobuf大家應該都很熟悉了,它是google出品的一種資訊交換格式,可以將其看做是一種序列化的方式。它是語言中立、平臺中立、可擴充套件的結構化資料序列化機制,和XML類似,但是比XML更小、更快、更簡單。
netty對protobuf的支援在於可以將protobuf中的message和MessageLite物件跟ByteBuf進行轉換。
protobuf的兩個編碼器也是message到message直接的轉換,所以也需要使用frame detection。當然你也可以使用其他的frame detection比如LengthFieldPrepender和LengthFieldBasedFrameDecoder如下所示:
ChannelPipeline pipeline = ...;
// Decoders
pipeline.addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
pipeline.addLast("protobufDecoder",
new ProtobufDecoder(MyMessage.getDefaultInstance()));
// Encoder
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("protobufEncoder", new ProtobufEncoder());
其中LengthFieldPrepender會自動給欄位前面加上一個長度欄位:
之前:
+----------------+
| "HELLO, WORLD" |
+----------------+
之後:
+--------+----------------+
+ 0x000C | "HELLO, WORLD" |
+--------+----------------+
當然netty為protobuf準備了兩個專門的frame detection,他們是ProtobufVarint32FrameDecoder和ProtobufVarint32LengthFieldPrepender。在講解這兩個類之前,我們需要了解一下protobuf中的Base 128 Varints。
什麼叫Varints呢?就是序列化整數的時候,佔用的空間大小是不一樣的,小的整數佔用的空間小,大的整數佔用的空間大,這樣不用固定一個具體的長度,可以減少資料的長度,但是會帶來解析的複雜度。
那麼怎麼知道這個資料到底需要幾個byte呢?在protobuf中,每個byte的最高位是一個判斷位,如果這個位被置位1,則表示後面一個byte和該byte是一起的,表示同一個數,如果這個位被置位0,則表示後面一個byte和該byte沒有關係,資料到這個byte就結束了。
舉個例子,一個byte是8位,如果表示的是整數1,那麼可以用下面的byte來表示:
0000 0001
如果一個byte裝不下的整數,那麼就需要使用多個byte來進行連線操作,比如下面的資料表示的是300:
1010 1100 0000 0010
為什麼是300呢?首先看第一個byte,它的首位是1,表示後面還有一個byte。再看第二個byte,它的首位是0,表示到此就結束了。我們把判斷位去掉,變成下面的數字:
010 1100 000 0010
這時候還不能計算資料的值,因為在protobuf中,byte的位數是反過來的,所以我們需要把上面的兩個byte交換一下位置:
000 0010 010 1100
也就是:
10 010 1100
=256 + 32 + 8 + 4 = 300
在protobuf中一般使用Varint作為欄位的長度位,所以netty提供了ProtobufVarint32LengthFieldPrepender和ProtobufVarint32FrameDecoder對ByteBuf進行轉換。
比如為ByteBuf新增varint的length:
BEFORE ENCODE (300 bytes) AFTER ENCODE (302 bytes)
+---------------+ +--------+---------------+
| Protobuf Data |-------------->| Length | Protobuf Data |
| (300 bytes) | | 0xAC02 | (300 bytes) |
+---------------+ +--------+---------------+
解碼的時候刪除varint的length欄位:
BEFORE DECODE (302 bytes) AFTER DECODE (300 bytes)
+--------+---------------+ +---------------+
| Length | Protobuf Data |----->| Protobuf Data |
| 0xAC02 | (300 bytes) | | (300 bytes) |
+--------+---------------+ +---------------+
serialization
序列化就是把物件轉換成二進位制資料,事實上所有的codec都可以成為序列化。他們提供了物件和byte之間的轉換方法。
netty也提供了兩個物件的轉換方法:ObjectDecoder和ObjectEncoder。
要注意的是,這兩個物件和JDK自帶的ObjectInputStream和ObjectOutputStream,是不相容的,如果要相容,可以使用CompactObjectInputStream、CompactObjectOutputStream和CompatibleObjectEncoder。
string
String是我們最常使用到的物件,netty為string提供了StringDecoder和StringEncoder。
同樣的,在使用這兩個類之前,需要將訊息進行轉換,通常使用的是 LineBasedFrameDecoder按行進行轉換:
ChannelPipeline pipeline = ...;
// Decoders
pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(80));
pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
// Encoder
pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
xml
xml也是一個非常常用的格式,但是它的體積會比較大,現在應該用的比較少了。netty提供了一個XmlFrameDecoder來進行解析。
因為xml有自己的開始和結束符,所以不需要再做frame detection,直接轉換即可,如:
+-----+-----+-----------+
| <an | Xml | Element/> |
+-----+-----+-----------+
轉換成:
+-----------------+
| <anXmlElement/> |
+-----------------+
+-----+-----+-----------+-----+----------------------------------+
| <an | Xml | Element/> | <ro | ot><child>content</child></root> |
+-----+-----+-----------+-----+----------------------------------+
轉換成:
+-----------------+-------------------------------------+
| <anXmlElement/> | <root><child>content</child></root> |
+-----------------+-------------------------------------+
都是可以的。
總結
netty提供了很多優秀的codec來適配各種應用協議,大家可以多用用,找找不同協議的不同之處。
本文已收錄於 http://www.flydean.com/16-netty-buildin-codec-common/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!