2.2.4. LengthFieldBasedFrameDecoder解码器

了解TCP通信机制的读者应该都知道TCP底层的粘包和拆包,当我们在接收消息的时候,显示不能认为读取到的报文就是个整包消息,特别是对于采用非阻塞I/O和长连接通信的程序。

如何区分一个整包消息,通常有如下4种做法:

1) 固定长度,例如每120个字节代表一个整包消息,不足的前面补位。解码器在处理这类定常消息的时候比较简单,每次读到指定长度的字节后再进行解码;

2) 通过回车换行符区分消息,例如HTTP协议。这类区分消息的方式多用于文本协议;

3) 通过特定的分隔符区分整包消息;

4) 通过在协议头/消息头中设置长度字段来标识整包消息。

前三种解码器之前的章节已经做了详细介绍,下面让我们来一起学习最后一种通用解码器-LengthFieldBasedFrameDecoder。

大多数的协议(私有或者公有),协议头中会携带长度字段,用于标识消息体或者整包消息的长度,例如SMPP、HTTP协议等。由于基于长度解码需求的通用性,以及为了降低用户的协议开发难度,Netty提供了LengthFieldBasedFrameDecoder,自动屏蔽TCP底层的拆包和粘包问题,只需要传入正确的参数,即可轻松解决“读半包“问题。

下面我们看看如何通过参数组合的不同来实现不同的“半包”读取策略。第一种常用的方式是消息的第一个字段是长度字段,后面是消息体,消息头中只包含一个长度字段。它的消息结构定义如图所示:

图2-3 解码前的字节缓冲区(14字节)

使用以下参数组合进行解码:

1) lengthFieldOffset = 0;

2) lengthFieldLength = 2;

3) lengthAdjustment = 0;

4) initialBytesToStrip = 0。

解码后的字节缓冲区内容如图所示:

图2-4 解码后的字节缓冲区(14字节)

通过ByteBuf.readableBytes()方法我们可以获取当前消息的长度,所以解码后的字节缓冲区可以不携带长度字段,由于长度字段在起始位置并且长度为2,所以将initialBytesToStrip设置为2,参数组合修改为:

1) lengthFieldOffset = 0;

2) lengthFieldLength = 2;

3) lengthAdjustment = 0;

4) initialBytesToStrip = 2。

解码后的字节缓冲区内容如图所示:

图2-5 跳过长度字段解码后的字节缓冲区(12字节)

解码后的字节缓冲区丢弃了长度字段,仅仅包含消息体,对于大多数的协议,解码之后消息长度没有用处,因此可以丢弃。

在大多数的应用场景中,长度字段仅用来标识消息体的长度,这类协议通常由消息长度字段+消息体组成,如上图所示的几个例子。但是,对于某些协议,长度字段还包含了消息头的长度。在这种应用场景中,往往需要使用lengthAdjustment进行修正。由于整个消息(包含消息头)的长度往往大于消息体的长度,所以,lengthAdjustment为负数。图2-6展示了通过指定lengthAdjustment字段来包含消息头的长度:

1) lengthFieldOffset = 0;

2) lengthFieldLength = 2;

3) lengthAdjustment = -2;

4) initialBytesToStrip = 0。

解码之前的码流:

图2-6 包含长度字段自身的码流

解码之后的码流:

图2-7 解码后的码流

由于协议种类繁多,并不是所有的协议都将长度字段放在消息头的首位,当标识消息长度的字段位于消息头的中间或者尾部时,需要使用lengthFieldOffset字段进行标识,下面的参数组合给出了如何解决消息长度字段不在首位的问题:

1) lengthFieldOffset = 2;

2) lengthFieldLength = 3;

3) lengthAdjustment = 0;

4) initialBytesToStrip = 0。

其中lengthFieldOffset表示长度字段在消息头中偏移的字节数,lengthFieldLength 表示长度字段自身的长度,解码效果如下:

解码之前:

图2-8 长度字段偏移的原始码流

解码之后:

图2-9长度字段偏移解码后的码流

由于消息头1的长度为2,所以长度字段的偏移量为2;消息长度字段Length为3,所以lengthFieldLength值为3。由于长度字段仅仅标识消息体的长度,所以lengthAdjustment和initialBytesToStrip都为0。

最后一种场景是长度字段夹在两个消息头之间或者长度字段位于消息头的中间,前后都有其它消息头字段,在这种场景下如果想忽略长度字段以及其前面的其它消息头字段,则可以通过initialBytesToStrip参数来跳过要忽略的字节长度,它的组合配置示意如下:

1) lengthFieldOffset = 1;

2) lengthFieldLength = 2;

3) lengthAdjustment = 1;

4) initialBytesToStrip = 3。

解码之前的码流(16字节):

图2-10长度字段夹在消息头中间的原始码流(16字节)

解码之后的码流(13字节):

图2-11长度字段夹在消息头中间解码后的码流(13字节)

由于HDR1的长度为1,所以长度字段的偏移量lengthFieldOffset为1;长度字段为2个字节,所以lengthFieldLength为2。由于长度字段是消息体的长度,解码后如果携带消息头中的字段,则需要使用lengthAdjustment进行调整,此处它的值为1,代表的是HDR2的长度,最后由于解码后的缓冲区要忽略长度字段和HDR1部分,所以lengthAdjustment为3。解码后的结果为13个字节,HDR1和Length字段被忽略。

事实上,通过4个参数的不同组合,可以达到不同的解码效果,用户在使用过程中可以根据业务的实际情况进行灵活调整。

由于TCP存在粘包和组包问题,所以通常情况下用户需要自己处理半包消息。利用LengthFieldBasedFrameDecoder解码器可以自动解决半包问题,它的习惯用法如下:

pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65536,0,2));
pipeline.addLast("UserDecoder", new UserDecoder());

在pipeline中增加LengthFieldBasedFrameDecoder解码器,指定正确的参数组合,它可以将Netty的ByteBuf解码成整包消息,后面的用户解码器拿到的就是个完整的数据报,按照逻辑正常进行解码即可,不再需要额外考虑“读半包”问题,降低了用户的开发难度。

原文: http://www.infoq.com/cn/articles/netty-codec-framework-analyse/

netty通用解码器LengthFieldBasedFrameDecoder的更多相关文章

  1. Netty学习(六)-LengthFieldBasedFrameDecoder解码器

    在TCP协议中我们知道当我们在接收消息时候,我们如何判断我们一次读取到的包就是整包消息呢,特别是对于使用了长连接和使用了非阻塞I/O的程序.上节我们也说了上层应用协议为了对消息进行区分一般采用4种方式 ...

  2. netty的解码器和粘包拆包

    Tcp是一个流的协议,一个完整的包可能会被Tcp拆成多个包进行发送,也可能把一个小的包封装成一个大的数据包发送,这就是所谓的粘包和拆包问题 粘包.拆包出现的原因: 在流传输中出现,UDP不会出现粘包, ...

  3. netty的解码器与粘包和拆包

    tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 假设客户端分别发送数据包D1和D2给服务端,由于服务 ...

  4. netty自定义解码器

    在socket传输通信中容易丢包问题,什么半包问题,这些都是很正常的问题,处理方法就是定义自己的编解码规则了,让每次接收按定义好的规则为一个完整包作为数据源即可. 下面个例子就是netty自定义的一个 ...

  5. Netty的ByteToMessageDecoder/LengthFieldBasedFrameDecoder

    是个inbound handler,channelRead方法里面,用一个bytebuf(cumulation)来把下一个数据包和当前这一个拼在一起,以免同一个请求被拆包.然后callDecode,里 ...

  6. NETTY 编码器介绍

    1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称为序列化(serialization),它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 反之,解码(Decod ...

  7. 【转】Netty系列之Netty编解码框架分析

    http://www.infoq.com/cn/articles/netty-codec-framework-analyse/ 1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称 ...

  8. Netty系列之Netty编解码框架分析

    1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称为序列化(serialization),它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 反之,解码(Decod ...

  9. netty之LengthFieldBasedFrameDecoder解码器

    官方api:http://netty.io/4.1/api/io/netty/handler/codec/LengthFieldBasedFrameDecoder.html package com.e ...

随机推荐

  1. Exercise02_01

    import java.util.Scanner; public class Out { public static void main(String[] args){ Scanner input = ...

  2. 收藏起来,史上最全的 MySQL 高性能优化实战总结!

    转自:https://mp.weixin.qq.com/s/sRsJzFO9dPtKhovJNWN3Dg 一.前言 MySQL 对于很多 Linux 从业者而言,是一个非常棘手的问题,多数情况都是因为 ...

  3. 使用.Net中的WeakDictionary — ConditionalWeakTable

    有的时候,我们需要给某些数据添加一些附加信息,一种常用的做法是使用一个Dictionary在填充这些附加信息如: var data = new Data();    var tag = new Tag ...

  4. 调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置 ------符号文件

    http://www.cnblogs.com/lyhabc/p/4184708.html

  5. Word2010插入页码分节符

    不是分页符,分节符主要是为了插入前后两种不同类型的页码,比如前面查罗马数字,从目录开始插入阿拉伯数字.                 

  6. Delphi 使窗体Showmodal后可以操作其他窗体

    对话框ShowModal之后不能操作其它窗口,实际上是因为Windows Disable了其它窗口.所以当你需要在模态对话框中访问其它已经可见的窗口时,需要用EnableWindow API来激活对应 ...

  7. RMAN备份恢复 控制文件和归档日志丢失情况

    RMAN> backup current controlfile tag='bak_ctlfile' format='/home/oracle/backup/bak_ctl_%U_%T'; al ...

  8. DEDECMS爆严重安全漏洞

    简要描述: 众所周知,因使用简单.客户群多,织梦CMS一直被爆出许多漏洞.“DEDECMS爆严重安全漏洞,近期官方会发布相关补丁,望大家及时关注补丁动态.” 详细说明: http://www.xx.c ...

  9. HBase 写优化之 BulkLoad 实现数据快速入库

    在第一次建立Hbase表的时候,我们可能需要往里面一次性导入大量的初始化数据.我们很自然地想到将数据一条条插入到Hbase中,或者通过MR方式等.但是这些方式不是慢就是在导入的过程的占用Region资 ...

  10. Amixer 控制声音

    amixer set Master XXXX 就可以直接控制主声卡属性 amixer set Master 20 #设置主声卡声音为 20 amixer set Master off #关闭主声卡(静 ...