本文主题:编码和解码,或者说是数据从一种特定协议的格式到另一种的转换。这些任务通常由​​编解码器​​组件处理

Netty 提供了多种组件,简化了为支持广泛协议而创建自定义编解码器的过程。

若你正在构建一个基于 Netty 的邮件服务器,那就会发现 Netty 对于编解码器的支持对于实现 POP3、IMAP 和 SMTP 协议来说是多么宝贵!

Netty如何实现高效且万能的解码器?_java

0 什么是编解码器

Netty如何实现高效且万能的解码器?_http_02

每个网络应用程序都必须定义


  • 如何解析在两个节点之间来回传输的原始字节
  • 如何将其和目标应用程序的数据格式做相互转换

这种转换逻辑由编解码器处理,编解码器由编码器和解码器组成,它们每种都可将字节流从一种格式转换为另一种。

它们的区别是什么?

Netty如何实现高效且万能的解码器?_java_03

若将消息看作对于特定应用程序具有具体含义的结构化的字节序列 — 它的数据。那


  • 编码器就是将消息转换为适合于传输的格式(最可能的就是字节流)
  • 对应的解码器则是将网络字节流转换回应用程序的消息格式

因此,编码器操作出站数据,解码器处理入站数据。


接下来让我们研究一下 Netty 所提供的用于实现这两种组件的类。

Netty如何实现高效且万能的解码器?_java

1 Netty解码

Netty如何实现高效且万能的解码器?_http_02

Netty如何实现高效且万能的解码器?_netty_06Netty 的解码器类:


  • 将字节解码为消息
    ByteToMessageDecoder 和 ReplayingDecoder


将一种消息类型解码为另一种

MessageToMessageDecoder



解码器负责​​将入站数据从一种格式转到另一种​​,所以 Netty 解码器实

现了 ​​ChannelInboundHandler​​ 也很自然。

什么时候会用解码器?

Netty如何实现高效且万能的解码器?_java_03

每当需为 ​​ChannelPipeline​​ 中的下一个 ​​ChannelInboundHandler​​ 转换入站数据时。

得益于​​ChannelPipeline​​ 的设计,可以将多个解码器连接在一起,以实现任意复杂的转换逻辑,这也是 Netty 是如何支持代码的模块化以及复用的一个很好的例子。

案例代码

Netty如何实现高效且万能的解码器?_数据库_08

Netty如何实现高效且万能的解码器?_数据库_09

Netty如何实现高效且万能的解码器?_netty_10

Netty如何实现高效且万能的解码器?_java

2 抽象解码器 ByteToMessageDecoder

Netty如何实现高效且万能的解码器?_http_02

2.1 示例

Netty如何实现高效且万能的解码器?_java_03

Netty 提供抽象基类:ByteToMessageDecoder,将字节解码为消息(或另一个字节序列)。

由于​​你不可能知道远程节点是否会一次性发送一个完整消息​​,所以该类会​​缓冲入站数据​​,直到它准备好处理。

  • ByteToMessageDecoderAPINetty如何实现高效且万能的解码器?_数据库_14

假设你接收了一个包含简单 int 的字节流,每个 int 都需要被单独处理

在这种情况下,你需要从入站​​ByteBuf​​中读取每个 int,并将它传递给​​ChannelPipeline​​ 中的下一个 ​​ChannelInboundHandler​

为了解码这个字节流,你要扩展 ​​ByteToMessageDecoder​​类(原子类型的 int 在被添加到 List 中时,会被自动装箱为 Integer)

Netty如何实现高效且万能的解码器?_java_15

每次从入站 ByteBuf 中读取 4 字节,将其解码为一个 int,然后将它添加到一个 List 中

当没有更多的元素可以被添加到该 List 中时,它的内容将会被发送给下一个 Channel- InboundHandler

  • ToIntegerDecoder类扩展了ByteToMessageDecoder

    Netty如何实现高效且万能的解码器?_数据库_16

    虽然​​ByteToMessageDecoder​​可以很简单地实现这种模式,但是你可能会发现,在调用 ​​readInt()​​前不得不验证所输入的 ByteBuf 是否具有足够的数据有点繁琐

    在下一节中, 我们将讨论 ReplayingDecoder,它是一个特殊的解码器,以少量的开销消除了这个步骤

2.2 源码解析

Netty如何实现高效且万能的解码器?_java_03

Netty如何实现高效且万能的解码器?_http_18

下面开始解析解码流程的源码:

Netty如何实现高效且万能的解码器?_数据库_19

2.2.1 累加字节流

Netty如何实现高效且万能的解码器?_数据库_20

Netty如何实现高效且万能的解码器?_http_21

其中的​​cumulator​​ 为

Netty如何实现高效且万能的解码器?_java_22

看一下这个​​MERGE_CUMULATOR​

Netty如何实现高效且万能的解码器?_数据库_23

Netty如何实现高效且万能的解码器?_数据库_19

2.2.2 调用子类 decode 方法进行解析

Netty如何实现高效且万能的解码器?_数据库_20

进入该方法查看源码

Netty如何实现高效且万能的解码器?_编程语言_26

Netty如何实现高效且万能的解码器?_编程语言_27

Netty如何实现高效且万能的解码器?_数据库_19

2.2.2 将解析到的 ByteBuf 向下传播

Netty如何实现高效且万能的解码器?_数据库_20

Netty如何实现高效且万能的解码器?_编程语言_30注意到上图中的如下代码段:

Netty如何实现高效且万能的解码器?_http_31

Netty如何实现高效且万能的解码器?_java_32

编解码器中的引用计数

Netty如何实现高效且万能的解码器?_java_03

对于编码器和解码器,一旦消息被编码或解码,它就会被 ​​ReferenceCountUtil.release(message)​​调用自动释放。

若需要保留引用以便稍后使用,可调用 ​​ReferenceCountUtil.retain(message)​​,这会增加该引用计数,从而防止该消息被释放。

Netty如何实现高效且万能的解码器?_java

3 固定长度解码器

Netty如何实现高效且万能的解码器?_http_02

Netty如何实现高效且万能的解码器?_netty_36

Netty如何实现高效且万能的解码器?_java

4 行解码器

Netty如何实现高效且万能的解码器?_http_02

4.1 定位行尾

Netty如何实现高效且万能的解码器?_java_03

Netty如何实现高效且万能的解码器?_java_40

Netty如何实现高效且万能的解码器?_http_41

4.2 非丢弃模式

Netty如何实现高效且万能的解码器?_java_03

Netty如何实现高效且万能的解码器?_java_43

Netty如何实现高效且万能的解码器?_java_44

找到换行符

Netty如何实现高效且万能的解码器?_netty_45

找不到换行符

Netty如何实现高效且万能的解码器?_netty_46

Netty如何实现高效且万能的解码器?_java_47

4.3 丢弃模式

Netty如何实现高效且万能的解码器?_java_03

Netty如何实现高效且万能的解码器?_http_49

找到换行符

Netty如何实现高效且万能的解码器?_http_50

找不到换行符

Netty如何实现高效且万能的解码器?_java_51

参考

  • 《Netty实战》

Netty如何实现高效且万能的解码器?_java_52

目前交流群已有​ 800+​人,旨在促进技术交流,可关注公众号

Netty如何实现高效且万能的解码器?_http_53