LengthFieldBasedFrameDecoder 详解

疯狂创客圈 Java 分布式聊天室【 亿级流量】实战系列之 -31【 博客园 总入口


写在前面

​ 大家好,我是作者尼恩。目前和几个小伙伴一起,组织了一个高并发的实战社群【疯狂创客圈】。正在开始高并发、亿级流程的 IM 聊天程序 学习和实战

有的小伙伴对帧解码器FrameDecoder ,尤其是LengthFieldBasedFrameDecoder(自定义长度帧解码器) 不是太了解,尤其是觉得LengthFieldBasedFrameDecoder 参数多,不理解。

这里单独撰文,对LengthFieldBasedFrameDecoder 的参数,进行重点介绍。看完之后,就会彻底的了解了。

1.1.1. 解码器:FrameDecoder

前面所讲的解码器,在获取入站数据时,都是通过ByteBuf的基础类型读取方法,读取到是基础的数据类型,比如int整数。如果在解码时,读取的不是基础类型,而是非常基础的二进制数据,该如何处理呢?

大家都知道,TCP协议是个“流”性质协议,它的底层根据二进制缓冲区的实际情况进行包的划分,会把上层(Netty层)的ByteBuf包,进行重新的划分和重组,组成一帧一帧的二进制数据。换句话说,一个上层Netty中的 ByteBuf包,可能会被TCP底层拆分成多个二进制数据帧进行发送;也有可能,底层将多个小的ByteBuf包,封装成一个大的底层数据帧发送出去。

问题来了:如何从底层的二进制数据帧中,界定出来上层数据包的边界,也即是上层包的起点和末尾呢?别急,界定的办法,还是很多的。比如说,简单一点方法就是规定上层数据包的长度。例如,规定每个上层数据包的长度为100byte。再比如说,可以规定上层包的分割符号,比如换行符。无论采用什么方法,最为重要的是,发送方和接收方,在界定方法上必须保持一致。

Netty中,提供了几个重要的可以直接使用的帧解码器。这里先介绍一个最为基础的,它就是LineBasedFrameDecoder。LineBasedFrameDecoder的工作原理很简单,依次遍历原始ByteBuf(代表底层帧)中的可读字节,判断看是否存在“\n”或者“\r\n”换行符,也就是上层包的边界的分割符。如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。同时,它支持配置上层包的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常。

下面演示一下LineBasedFrameDecoder的使用,代码如下:


  1. /**
  2. * create by 尼恩 @ 疯狂创客圈
  3. **/
  4. package com.crazymakercircle.NettyTest;
  5. //...
  6. public class TestDecoder {
  7. @Test
  8. public void testLineBasedFrameDecoder() {
  9. //...
  10. ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
  11. protected void initChannel(EmbeddedChannel ch) {
  12. ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
  13. ch.pipeline().addLast(new StringDecoder());
  14. ch.pipeline().addLast(new StringProcessHandler());
  15. }
  16. };
  17. EmbeddedChannel channel = new EmbeddedChannel(i);
  18. for (int j = 0; j < 100; j++) {
  19. ByteBuf buf = Unpooled.buffer();
  20. String s = "I am " + j;
  21. buf.writeBytes(s.getBytes("UTF-8"));
  22. buf.writeBytes("\r\n".getBytes("UTF-8"));
  23. channel.writeInbound(buf);
  24. }
  25. //...
  26. }

实例中,向channel写入100个入站数据包,每一个入站包都以"\r\n"回车换行符作为结束。channel的LineBasedFrameDecoder 解码器,会将"\r\n"作为分割符,分割出一个一个的入站ByteBuf,然后发送给StringDecoder。StringDecoder会将分割好的ByteBuf二进制数据,转成字符串,发送给StringProcessHandler 。最后,由StringProcessHandler负责将字符串展示出来。

这里,LineBasedFrameDecoder 和StringDecoder 都是Netty自带的类。特别要说下的,就是StringDecoder,它的作用是将接收到ByteBuf二进制数据,转换成字符串。另外,LineBasedFrameDecoder ,是一个非常简单的帧解码器,包含此解码器在内,Netty中比较常用的帧解码器,大致如下:

(1)固定长度帧解码器 - FixedLengthFrameDecoder

适用场景:每个上层数据包的长度,都是固定的,比如 100。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会把底层帧,拆分成一个个长度为 100 的数据包 (ByteBuf),发送到下一个 channelHandler入站处理器。

(2)行分割帧解码器 - LineBasedFrameDecoder

适用场景:每个上层数据包,使用换行符或者回车换行符做为边界分割符。发送端发送的时候,每个数据包之间以换行符/回车换行符作为分隔。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会使用换行分隔符,把底层帧分割成一个一个完整的应用层数据包,发送到下一站。前面的例子,已经对这个解码器进行了演示。

(3)自定义分隔符帧解码器 - DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder 是LineBasedFrameDecoder的通用版本。不同之处在于,这个解码器,可以自定义分隔符,而不是局限于换行符。如果使用这个解码器,在发送的时候,末尾必须带上对应的分隔符。

(4)自定义长度帧解码器 - LengthFieldBasedFrameDecoder

这是一种基于灵活长度的解码器。在数据包中,加了一个长度字段(长度域),保存上层包的长度。解码的时候,会按照这个长度,进行上层ByteBuf应用包的提取。

1.1.1. 难点:自定义长度帧解码器

在前面的四个帧解码器中,第四个解码器LengthFieldBasedFrameDecoder(自定义长度帧解码器)的参数比较多,比较难,同时也比较重要,这里对其进行重点介绍。

下面是一个简单的使用实例,代码如下:

  1. /**
  2. * create by 尼恩 @ 疯狂创客圈
  3. **/
  4. package com.crazymakercircle.NettyTest;
  5. public class TestDecoder {
  6. //...
  7. @Test
  8. public void testLengthFieldBasedFrameDecoder() {
  9. try {
  10. LengthFieldBasedFrameDecoder spliter=new LengthFieldBasedFrameDecoder(1024,0,4,0,4);
  11. ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
  12. protected void initChannel(EmbeddedChannel ch) {
  13. ch.pipeline().addLast(spliter);
  14. ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));
  15. ch.pipeline().addLast(new StringProcessHandler());
  16. }
  17. };
  18. EmbeddedChannel channel = new EmbeddedChannel(i);
  19. for (int j = 0; j < 100; j++) {
  20. ByteBuf buf = Unpooled.buffer();
  21. String s = "呵呵,I am " + j;
  22. byte[] bytes = s.getBytes("UTF-8");
  23. buf.writeInt(bytes.length);
  24. buf.writeBytes(bytes);
  25. channel.writeInbound(buf);
  26. }
  27. //...
  28. }

上面用到的自定义长度解码器LengthFieldBasedFrameDecoder构造器,涉及5个参数,都与长度域(数据包中的长度字段)相关,具体介绍如下:

(1) maxFrameLength - 发送的数据包最大长度;

(2) lengthFieldOffset - 长度域偏移量,指的是长度域位于整个数据包字节数组中的下标;

(3) lengthFieldLength - 长度域的自己的字节数长度。

(4) lengthAdjustment – 长度域的偏移量矫正。 如果长度域的值,除了包含有效数据域的长度外,还包含了其他域(如长度域自身)长度,那么,就需要进行矫正。矫正的值为:包长 - 长度域的值 – 长度域偏移 – 长度域长。

(5) initialBytesToStrip – 丢弃的起始字节数。丢弃处于有效数据前面的字节数量。比如前面有4个节点的长度域,则它的值为4。

在上面的例子中,自定义长度解码器的构造参数值如下:

  1. LengthFieldBasedFrameDecoder spliter=new LengthFieldBasedFrameDecoder(1024,0,4,0,4);

第一个参数为1024,表示数据包的最大长度为1024;第二个参数0,表示长度域的偏移量为0,也就是长度域放在了最前面,处于包的起始位置;第三个参数为4,表示长度域占用4个字节;第四个参数为0,表示长度域保存的值,仅仅为有效数据长度,不包含其他域(如长度域)的长度;第五个参数为4,表示最终的取到的目标数据包,抛弃最前面的4个字节数据,长度域的值被抛弃。

为了更加清楚的说明一下上面的规则,调整一下例子中的代码。在写入通道前,在数据包的最前面,加上两个字节,作为包头Head。另外,写入的长度值,包含长度域自身的长度,也就是加上4。 修改后的代码如下:

  1. /**
  2. * create by 尼恩 @ 疯狂创客圈
  3. **/
  4. //...
  5. for (int j = 0; j < 100; j++) {
  6. ByteBuf buf = Unpooled.buffer();
  7. String s = j+ " is me ,呵呵" ;
  8. byte[] bytes = s.getBytes("UTF-8");
  9. buf.writeChar(100);
  10. buf.writeInt(bytes.length+4);
  11. buf.writeBytes(bytes);
  12. }

为了完成正确的解码,需要调整自定义长度解码器的构造参数值,调整如下:

  1. LengthFieldBasedFrameDecoder spliter=new LengthFieldBasedFrameDecoder(1024,2,4,-4,6);

第一、第二、第三个参数比较简单,不再啰嗦。

第四个参数长度域的矫正值为 -4,为什么呢? 它计算的方法是:包长(X+2)- 长度域的值(X) – 长度域偏移(2) – 长度域长(4)= -4 。

这里假定长度域的值为X,那么包长为X+2。因为在这个例子中,长度域的值,已经包括了长度域的长度值。长度域值与整个包长度相比,就少了前面的Header的2个字节。按照公式进行计算,最终的值为 2-2-4 = -4 。

第五个参数丢弃的起始字节数为6,为什么呢? 因为,最终的有效的应用层数据,需要去掉前面的6个字节。其中,包括2个字节的Header,4个字节的长度域长。


LengthFieldBasedFrameDecoder的更多相关文章

  1. netty中LengthFieldBasedFrameDecoder的使用

    在org.jboss.netty.handler.codec.frame包中,有LengthFieldBasedFrameDecoder类用来解析带有长度属性的包,只要我们在传输协议中加入包的总长度就 ...

  2. Netty 中 LengthFieldBasedFrameDecoder 构造函数取值备忘

    public LengthFieldBasedFrameDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, ...

  3. Netty的ByteToMessageDecoder/LengthFieldBasedFrameDecoder

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

  4. 【转】netty源码分析之LengthFieldBasedFrameDecoder

    原文:https://www.jianshu.com/p/a0a51fd79f62 拆包的原理 关于拆包原理的上一篇博文 netty源码分析之拆包器的奥秘 中已详细阐述,这里简单总结下:netty的拆 ...

  5. netty之LengthFieldBasedFrameDecoder解码器

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

  6. netty通用解码器LengthFieldBasedFrameDecoder

    2.2.4. LengthFieldBasedFrameDecoder解码器 了解TCP通信机制的读者应该都知道TCP底层的粘包和拆包,当我们在接收消息的时候,显示不能认为读取到的报文就是个整包消息, ...

  7. LengthFieldBasedFrameDecoder 秒懂

    目录 写在前面 1.1.1. 解码器:FrameDecoder 1.1.1. 难点:自定义长度帧解码器 写在最后 疯狂创客圈 亿级流量 高并发IM 学习实战 疯狂创客圈 Java 分布式聊天室[ 亿级 ...

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

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

  9. Netty源码分析 (十一)----- 拆包器之LengthFieldBasedFrameDecoder

    本篇文章主要是介绍使用LengthFieldBasedFrameDecoder解码器自定义协议.通常,协议的格式如下: LengthFieldBasedFrameDecoder是netty解决拆包粘包 ...

  10. LengthFieldBasedFrameDecoder 参数说明

    LengthFieldBasedFrameDecoder 参数说明 举例 数据包格式为 body长度(4个)+14个字节的版本说明(字符串)+body 那么LengthFieldBasedFrameD ...

随机推荐

  1. [LeetCode] 683. K Empty Slots K个空槽

    There is a garden with N slots. In each slot, there is a flower. The N flowers will bloom one by one ...

  2. AI语音验证码识别

    欢迎使用AI语音验证码识别v4.0程序程序调用方法:http://code.hbadmin.com/?url=http://code.hbadmin.com/demo/2118534.wav [试听] ...

  3. python字符串各种颜色输出

    \033[1;31;40m      # 1是显示方式(可选),31是字体颜色,40m 是字体背景颜色: \033[0m           # 恢复终端默认颜色,即取消颜色设置: #!/usr/bi ...

  4. Qt Quick 常用元素:Textinput 与 TextEdit 文本编辑框

    一.Textinput Textinput 用于编辑一行文本,类似于 QLineEdit. font 分组属性允许你设置 Textlnput 元素所用字体的各种属性,包括字体族(family).大 小 ...

  5. 使用windows 上的远程连接来远程Linux Ubuntu系统的设置

    实验环境: Windows 10 , VMware Workstation ,Ubuntu16.04 1.root登录ubuntu,然后执行下面的命令 # root账户登录ubuntu ,执行下面的命 ...

  6. SpringBoot第四篇:整合JDBCTemplate

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/10868954.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   前面几篇文 ...

  7. ant-design自定义FormItem--上传文件组件

    自定义上传组件,只需要在内部的值变化之后调用props中的onChange方法就可以托管在From组件中, 此外为了保证,初始化值发生变化后组件也发生变化,需要检测initialValue 变化,这是 ...

  8. 集合类源码(五)Collection之BlockingQueue(LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue)

    LinkedTransferQueue 功能 全名 public class LinkedTransferQueue<E> extends AbstractQueue<E> i ...

  9. elasticsearch6设置默认分片数和副本数

    elasticsearch6设置索引的默认分片数和副本数已经不是在elasticsearch.yml文件中了,而是使用了一个索引模板的东西 curl -XPUT 'http://10.27.12.16 ...

  10. Password file not found:.../jmxremote.password

    jmxremote.password 在jdk/jre/lib/management/下,jmxremote.password.template复制,去掉.template后缀 在配置JMX远程访问的 ...