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. NPOI读取Excel2003,2007

    using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Lin ...

  2. Scala实战高手****第3课:在IDE下开发第一个Scala程序纯傻瓜式彻底透彻解析

  3. ajax变量作用域的问题

    需求:使得一个变量在ajax内外都有作用域 方法:设置为同步请求 async:false

  4. Java中泛型得到T.class

    例子: public class Test<T> { public Class<T> getTClass() { return (Class<T>) ((Param ...

  5. FTTB FTTC FTTH FTTO FSA

    FTTB Fiber to The Building 光纤到楼 FTTC Fiber to The Curb 光纤到路边 FTTH Fiber to The Home 光纤到家 FTTO Fiber ...

  6. Network Connection Lost When Windows 8 Goes To Sleep

    http://www.kapilarya.com/fix-network-connection-lost-when-windows-8-goes-to-sleep http://superuser.c ...

  7. 关于css解决俩边等高的问题(等高布局)

    等高布局 前段时间公司需哦一个后台管理系统,左侧是导航栏,右侧是content区域.然厚刚开始用的是js 去控制的,但是当页面的椰蓉过长的时候,有与js单线程,加载比较慢,就会有那么一个过程,查找了很 ...

  8. js原生创建模拟事件和自定义事件的方法

    让我万万没想到的是,原来<JavaScript高级程序设计(第3版)>里面提到的方法已经是过时的了.后来我查看了MDN,才找到了最新的方法. 模拟鼠标事件MDN上已经说得很清楚,尽管为了保 ...

  9. zclip结合easyui实现复制datagrid每行特定单元格数据的功能

    在easyui的datagrid里面,实现复制每行特定单元格的功能,关键是想想如何获取到每个单元格的数据,功能是点击按钮"复制",然后复制object的下载地址,截图如下所示: 进 ...

  10. Java笔记9:Spring简单Demo

      1 下载spring-framework-3.0.5.RELEASE-with-docs.zip和spring-framework-3.0.5.RELEASE-dependencies.zip,放 ...