写点什么

netty 系列之:netty 中的懒人编码解码器

发布于: 1 小时前

简介

netty 之所以强大,是因为它内置了很多非常有用的编码解码器,通过使用这些编码解码器可以很方便的搭建出非常强大的应用程序,今天给大家讲讲 netty 中最基本的内置编码解码器。

netty 中的内置编码器

在对 netty 的包进行引入的时候,我们可以看到 netty 有很多以 netty-codec 开头的 artifactId,统计一下,有这么多个:

netty-codecnetty-codec-httpnetty-codec-http2netty-codec-memcachenetty-codec-redisnetty-codec-socksnetty-codec-stompnetty-codec-mqttnetty-codec-haproxynetty-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 这个包的内容就比较丰富了,主要是对数据的压缩和解压缩服务。其支持的算法如下:

brotliBzip2FastLZJdkZlibLz4LzfSnappyZlibZstandard
复制代码

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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 1 小时前阅读数: 2
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:netty中的懒人编码解码器