3.Netty有哪些开箱即用的解码器
作为一个优秀的网络编程框架,Netty除了支持扩展自定义编解码器外,还提供了非常丰富的开箱即用的编解码器。尤其是针对我们上文1.2节中提过的三种解决「粘包/拆包问题」的方式,都有开箱即用的实现。
3.1固定长度解码器FixedLengthFrameDecoder
这个解码器上文已经提到过,对应1.2节中的「固定长度解码」,这里再稍微展开一下。
通过构造函数配置固定长度 frameLength,然后在decode时,按照frameLength 进行解码。
- 当读取到长度大小为 frameLength 的消息,那么解码器认为已经获取到了一个完整的消息。
- 当消息长度小于 frameLength,FixedLengthFrameDecoder 解码器会一直等后续数据包的到达,直至获得完整的消息。
3.2特殊分隔符解码DelimiterBasedFrameDecoder
这个解码器对应1.2节中的「特殊分隔符解码」,也是一个继承自ByteToMessageDecoder的解码器。
这个解码器会使用 1个 或 多个 符号delimiter 对传入的消息(ByteBuf)进行解码。
我们看一下构造器,了解一下几个重要参数。
- maxFranmeLength
maxFranmeLength 是待处理消息的最大长度限制。如果超过 maxFranmeLength 还没有检测到指定分隔符,将会抛出 TooLongFrameException。
- stripDelimiter
stripDelimiter是一个boolean类型, 用于判断解码后得到的消息是否移除分隔符。如果 stripDelimiter=false,那么解码后的消息内容就会保留分隔符信息。
- failFast
failFast是一个boolean类型。如果为true,那么消息在超出 maxFranmeLength 后,会立即抛出 TooLongFrameException。如果为false,那么会等到解码出一个完整的消息后才会抛出TooLongFrameException。
- delimiters
delimiters 的类型是 ByteBuf 数组,可以在构造器中同时传入多个分隔符,但是在解析时,最终会选择长度最短的分隔符进行消息拆分。
例如收到的数据为:
ABCD\nEFG\r\n
如果指定的分隔符为 \n 和 \r\n,那么会解码出两个消息。
ABCD EFG
如果指定的特定分隔符只有 \r\n,那么只会解码出一个消息:
ABCD\nEFG
3.3长度域解码器LengthFieIdBasedFrameDecoder
这个解码器是生产实践中运用比较广泛的一种(比如RocketMQ),相对复杂,但是特别灵活,基本能覆盖各种基于长度进行拆包的方案,比如1.2节中提到的「消息长度+内容」的方案。
使用这个解码器的时候,重点需要了解4个参数,掌握了参数的设置,就能快速实现不同的基于长度的拆包解码方案。
参数名 |
类型 |
含义 |
lengthFieldOffset |
int |
长度字段的偏移量。表示「长度域」的起始位置 |
lengthFieldLength |
int |
长度字段所占用的字节数 |
lengthAdjustment |
int |
消息长度的修正值。表示一些复杂协议中,会在「长度域」添加一些其他内容,如版本号、消息类型等,这就需要修正值进行修正处理 |
initialBytesToStrip |
int |
解码后需要跳过的初始字节数。表示消息内容数据的起始位置 |
1)解码方案一:基于消息长度 + 消息内容,解码结果不截断消息头
报文只包含消息长度 Length 和消息内容 Content 字段,其中 Length 为 16 进制表示,共占用 2 字节,Length 的值 0x000C 代表 Content 占用 12 字节。
参数名 |
取值 |
lengthFieldOffset |
0 |
lengthFieldLength |
2 |
lengthAdjustment |
0 |
initialBytesToStrip |
0(表示解码结果不截断消息头) |
解码示例:
2)解码方案二:基于消息长度 + 消息内容,解码结果截断
与方案一不同之处在于,解码结果会截断消息头(跳过2字节)
参数名 |
取值 |
lengthFieldOffset |
0 |
lengthFieldLength |
2 |
lengthAdjustment |
0 |
initialBytesToStrip |
2(表示跳过 Length 字段的字节长度,解码后 只包含 消息内容) |
解码示例:
3)解码方案三:基于消息头 + 消息长度 + 消息内容
消息起始位置添加特殊消息头,消息长度 Length字段 后移。
参数名 |
取值 |
lengthFieldOffset |
2 |
lengthFieldLength |
3 |
lengthAdjustment |
0 |
initialBytesToStrip |
0(表示解码结果不截断消息头) |
解码示例:
4)解码方案四:基于消息长度 + 消息头 + 消息内容
消息起始位置为消息长度 Length字段,后面并不直接添加 消息内容,而是先添加 消息头header,再添加 消息内容。
参数名 |
取值 |
lengthFieldOffset |
0 |
lengthFieldLength |
3 |
lengthAdjustment |
2 (Header1的长度) |
initialBytesToStrip |
0(表示解码结果不截断消息头) |
解码示例:
由于 Length 后面不是马上添加content,所以需要加上 lengthAdjustment(2 字节)才能得到 Header + Content 的内容(14 字节)。
4.小结
来简单回顾下吧。
本文主要介绍了ChannelHandler的一种典型应用场景——编解码器。
编解码器核心关注点在于「粘包/拆包」的处理,我们介绍了「粘包/拆包」产生的原因以及常用解决方案。然后说明了如何使用Netty框架实现自定义编解码器。
最后,介绍了Netty中非常好用的几个开箱即用的编解码器。
参考书目:
《Netty 实战》
《Netty4核心原理》