netty系列之:netty中的懶人編碼解碼器

簡介

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/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

歡迎關注我的公衆號:「程序那些事」,懂技術,更懂你!

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