深入理解 Netty-解码器架构与常用解码器 (二)

简介: 深入理解 Netty-解码器架构与常用解码器 (二)

常用的编解码器#


固定长度的解码器FixedLengthFrameDecoder#


他里面只维护着一个private final int frameLength;

使用时,我们通过构造函数传递给他,他就会按照下面的方式解码

我们看一下它的javaDoc


原始数据
 * +---+----+------+----+
 * | A | BC | DEFG | HI |
 * +---+----+------+----+
 如果frameLength==3
 * +-----+-----+-----+
 * | ABC | DEF | GHI |
 * +-----+-----+-----+


它的decode() 实现如下


protected Object decode(
    @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (in.readableBytes() < frameLength) {
    return null;
} else {
// 从in中截取 frameLength 长度的 字节流
    return in.readRetainedSlice(frameLength);
}
}


行解码器LineBasedFrameDecoder#


她会根据换行符进行解码, 无论用户发送过来的数据是以 \r\n 还是 \n 类型的换行符LineBasedFrameDecoder


使用:


public LineBasedFrameDecoder(final int maxLength) {
        this(maxLength, true, false);
    }
  public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
        this.maxLength = maxLength;
        this.failFast = failFast;
        this.stripDelimiter = stripDelimiter;
    }


第一个构造函数

  • 入参位置是我们指定的每一行最大的字节数, 超过了这个大小的所有行,将全部被丢弃
  • 默认跳过分隔符
  • 出现了超过最大值的行,不报异常


第二个构造函数

  • 入参1 是我们指定的每一行最大的字节数, 超过了这个大小的所有行,将全部被丢弃
  • 入参2 指定每次解析是否跳过换行符
  • 入参3 指定出现大于规定的最大字节数时是否报异常

看它重写的decode()的实现逻辑如下:


它总起来分成四种情况

  • 非丢弃模式
  • 找到了换行符
  • 如 readIndex + 换行符的位置 < maxLength 的关系 --> 解码
  • 如 readIndex + 换行符的位置 > maxLength的关系 --> 丢弃
  • 未找到换行符
  • 如果可解析的长度 > maxLength --> 丢弃
  • 丢弃模式
  • 找到了换行符
  • 丢弃
  • 未找到换行符
  • 丢弃


基于分隔符的解码器DelimiterBasedFrameDecoder#


它主要有这几个成员变量, 根据这几个成员变量,可以选出使用它哪个构造函数


private final ByteBuf[] delimiters;  分隔符,数组
private final int maxFrameLength;    每次能允许的最大解码长度
private final boolean stripDelimiter;  是否跳过分隔符
private final boolean failFast;      超过最大解码长度时,是否抛出异常
private boolean discardingTooLongFrame;  是否丢弃超过最大限度的帧
private int tooLongFrameLength;      记录超过最大范围的字节数值


分三步

  • 第一, 判断我们传递进入的分隔符是否是\n \r\n 如果是的话,就是用上面的, 行解码器
  • 第二步, 按照最细的力度进行解码, 比如, 我们有两个解码器, AB, 当前的readIndex 到A, 有2个字节, 到B有3个字节, 就会按照A进行解码
  • 解码


基于长度域的解码器LengthFieldBasedFrameDecoder#


通常我们在对特定的网络协议进行解码时会用到它,比如说,最典型的http协议, 虽然http协议看起来, 又有请求头,又有请求体,挺麻烦的,它在网络中依然是以字节流的方式进行传输

基于长度域,指的是在传输的协议中有一个 length字段,这个十六进制的字段记录的可能是整个协议的长度,也可能是消息体的长度, 我们根据具体情况使用不同的构造函数

如何使用呢? 最常用它下面的这个构造函数


public LengthFieldBasedFrameDecoder(
    int maxFrameLength,
    int lengthFieldOffset,
    int lengthFieldLength,
    int lengthAdjustment,
    int initialBytesToStrip) {
this(
        maxFrameLength,
        lengthFieldOffset, lengthFieldLength, lengthAdjustment,
        initialBytesToStrip, true);
}


使用它的前提是,知道这五个参数的意思

  • maxFrameLength 每次解码所能接受的最大帧的长度
  • lengthFieldOffset 长度域的偏移量

听着挺高大尚的, 偏移量, 说白了,就是在现有的这段字节数据中找个开始解码的位置, 大多数设为0, 意为,从o位置 开始解码


  • lengthFieldLength 字段域的长度, 根据lengthFieldOffset的初始值往后数lengthFieldLength个字节,这段范围解析出来的数值 可能是 长度域的大小,也可能是整个协议的大小(包括header,body...) 根据不同的协议不同
  • lengthAdjustment 矫正长度
  • initialBytesToStrip 需要取出的长度


下面是javaDoc给的例子


基于长度的拆包


* BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
 * +--------+----------------+      +--------+----------------+
 * | Length | Actual Content |----->| Length | Actual Content |
 * | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
 * +--------+----------------+      +--------+----------------+
 这是最简单的情况, 假定 Length的长度就是后面的 真正需要解码的内容
 现在的字节全部解码后是这样的  12HELLO, WORLD
 我们要做的就是区分出  12和HELLO, WORLD
 * lengthFieldOffset   = 0
 * lengthFieldLength   = 2 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = 0
 * initialBytesToStrip = 0
 意思就是:
  字节数组[lengthFieldOffset,lengthFieldLength]之间的内容转换成十进制,就是后面的字段域的长度
    00 0C ==> 12 
  这个12 意思就是 长度域的长度, 说白了 就是我们想要的 HELLO, WORLD 的长度
  这样一算,就分开了

基于长度的阶段拆包


* BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
 * +--------+----------------+      +----------------+
 * | Length | Actual Content |----->| Actual Content |
 * | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
 * +--------+----------------+      +----------------+
 情况2: 
 * lengthFieldOffset   = 0
 * lengthFieldLength   = 2 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = 0
 * initialBytesToStrip = 2
  意思就是
   字节数组[lengthFieldOffset,lengthFieldLength]之间的内容转换成十进制,就是后面的字段域的长度是
   00 0C ==> 12 
   这个12 意思就是 长度域的长度, 说白了 就是我们想要的 HELLO, WORLD 的长度
  然后,  从0开始 忽略 initialBytesToStrip, 就去除了 length ,只留下 HELLO, WORLD
 有时, 在某些其他协议中, length field 可能代表是整个消息的长度, 包括消息头
       在这种情况下,我们就得指定一个 非零的 lengthAdjustment 去调整
   * BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
 * +--------+----------------+      +--------+----------------+
 * | Length | Actual Content |----->| Length | Actual Content |
 * | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
 * +--------+----------------+      +--------+----------------+
 * lengthFieldOffset   = 0
 * lengthFieldLength   = 2 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = -2
 * initialBytesToStrip = 0
    意思就是
    字节数组[lengthFieldOffset,lengthFieldLength]之间的内容转换成十进制,表示整个协议的长度
    00 0C ==> 14  意味,协议全长 14
    现在还是不能区分开  Length 和 Actual Content
    公式: 数据包的长度 = 长度域 + lengthFieldOffset + lengthFieldLength +lengthAdjustment
    通过他可以算出 lengthAdjustment = -2

基于偏移长度的拆包


* BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
 * +----------+----------+----------------+      +----------+----------+----------------+
 * | Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
 * |  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
 * +----------+----------+----------------+      +----------+----------+----------------+
  这个例子和第一个例子很像,但是多了头
  我们想拿到后面消息长度的信息,就偏移过header
 * lengthFieldOffset   = 2
 * lengthFieldLength   = 3 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = 0
 * initialBytesToStrip = 0
  字节数组[lengthFieldOffset,lengthFieldLength]之间的内容转换成十进制, 表示长度域的长度
  在这里 整好跳过了 header 1,   0x00 00 0C 是三个字节
  也就是  字节数组[lengthFieldOffset,lengthFieldLength]=>[0,3]
  0x00 00 0C == 12 表示长度域是 12
  现在也成功区分开了 Header 1 和  Length 和 Actual Content
  分别是 2 3 12

基于可调整长度的拆包


BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
 * +----------+----------+----------------+      +----------+----------+----------------+
 * |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
 * | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
 * +----------+----------+----------------+      +----------+----------+----------------+
 * lengthFieldOffset   = 0
 * lengthFieldLength   = 3 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = 2
 * initialBytesToStrip = 0
  字节数组[lengthFieldOffset,lengthFieldLength]之间的内容转换成十进制, 表示长度域的长度
  也就是  字节数组[lengthFieldOffset,lengthFieldLength]=>[0,3]
  0x00 00 0C 是三个字节  
  0x00 00 0C == 12 表示长度域是 12 == 长度域的长度 就是 HELLO, WORLD的长度
  但是上面的图多了一个 两个字节长度的 Header 1
  下一步进行调整 
  公式: 数据包的长度 = 长度域 + lengthFieldOffset + lengthFieldLength +lengthAdjustment
  lengthAdjustment= 17-12-0-3=2

基于偏移可调整长度的截断拆包


* BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
 * +------+--------+------+----------------+      +------+----------------+
 * | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
 * | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
 * +------+--------+------+----------------+      +------+----------------+
 * lengthFieldOffset   = 1
 * lengthFieldLength   = 2 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = 1
 * initialBytesToStrip = 3
 lengthFieldOffset =1 偏移1字节 跨过 HDR1
 lengthFieldLength =2 从[1,2] ==> 0x000C =12 表示长度域的值
 看拆包后的结果,后面明显还多了个 HDR2 ,进行调整
 公式:  数据包值 = 长度域  + lengthFieldOffset+ lengthFieldLength + lengthAdjustment
 算出 lengthAdjustment = 16 - 12 - 1 - 2 = 1
 结果值只有 HDR2 和  Actual Content , 说明,前面通过 initialBytesToStrip 进行忽略
 initialBytesToStrip =3

基于偏移可调整长度的 变种 截断拆包


* BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
 * +------+--------+------+----------------+      +------+----------------+
 * | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
 * | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
 * +------+--------+------+----------------+      +------+----------------+
 * lengthFieldOffset   = 1
 * lengthFieldLength   = 2 // todo 每两个字节 表示一个数据包
 * lengthAdjustment    = -3
 * initialBytesToStrip = 3
 同样
 看结果,保留 HDR2 和 Actual Content
 lengthFieldOffset   = 1 表示跳过开头的 HDR1
 [1,2] ==> 00 10 , 算出的 长度域的值==10 很显然这不对
 10 < 13
 我们要想拆出后面的数据包就得在现有的基础上往左移动三个字节 -3个调整量


相关文章
|
6月前
|
移动开发 编解码 Java
Netty编码器和解码器
Netty从底层Java通道读到ByteBuf二进制数据,传入Netty通道的流水线,随后开始入站处理。在入站处理过程中,需要将ByteBuf二进制类型解码成Java POJO对象。这个解码过程可以通过Netty的Decoder解码器去完成。在出站处理过程中,业务处理后的结果需要从某个Java POJO对象编码为最终的ByteBuf二进制数据,然后通过底层 Java通道发送到对端。在编码过程中,需要用到Netty的Encoder编码器去完成数据的编码工作。
|
3月前
|
移动开发 网络协议 算法
(十)Netty进阶篇:漫谈网络粘包、半包问题、解码器与长连接、心跳机制实战
在前面关于《Netty入门篇》的文章中,咱们已经初步对Netty这个著名的网络框架有了认知,本章的目的则是承接上文,再对Netty中的一些进阶知识进行阐述,毕竟前面的内容中,仅阐述了一些Netty的核心组件,想要真正掌握Netty框架,对于它我们应该具备更为全面的认知。
209 2
|
6月前
|
移动开发 网络协议 Java
通信密码学:探秘Netty中解码器的神奇力量
通信密码学:探秘Netty中解码器的神奇力量
197 0
|
6月前
|
编解码
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
95 0
|
6月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
160 0
|
6月前
|
编解码 安全 前端开发
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
255 0
|
6月前
|
前端开发 网络协议 Dubbo
Netty - 回顾Netty高性能原理和框架架构解析
Netty - 回顾Netty高性能原理和框架架构解析
289 0
|
6月前
|
存储 编解码 Java
Netty使用篇:自定义编解码器
Netty使用篇:自定义编解码器
|
6月前
|
设计模式 JSON 编解码
Netty使用篇:编解码器
Netty使用篇:编解码器
|
设计模式 缓存 网络协议
Netty整体介绍和架构认知(一)
Netty整体介绍和架构认知
12356 3
下一篇
无影云桌面