首页 > 其他分享 >【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?

时间:2023-07-13 15:31:46浏览次数:56  
标签:Netty 浅谈 buffer 字段 WORLD 长度 字节 HELLO 进阶

前言

本篇博文是《从0到1学习 Netty》中进阶系列的第二篇博文,主要内容是通过不同的应用案例来了解 LengthFieldBasedFrameDecoder 是如何处理不同的消息,实现自动分割,往期系列文章请访问博主的 Netty 专栏,博文中的所有代码全部收集在博主的 GitHub 仓库中;

介绍

LengthFieldBasedFrameDecoder 是 Netty 中的一个解码器,用于处理粘包和半包情况。它能根据指定的长度字段解析数据帧,将输入的字节流分割成一系列固定大小的帧 Frames,并且每个帧的大小可以根据帧头信息中指定的长度进行动态调整。通过这种方式,LengthFieldBasedFrameDecoder 能够自动地识别和处理 TCP 协议中存在的粘包和半包情况。

使用 LengthFieldBasedFrameDecoder 需要指定几个参数,包括要解码的最大数据包长度、长度域的偏移量、长度域所占用的字节数等。在解码过程中,解码器会读取指定位置的长度域,并计算出数据包的实际大小,然后从输入流中截取相应长度的字节作为一个完整的数据包进行处理。

LengthFieldBasedFrameDecoder 的构造器代码如下所示:

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

参数解析:

  • maxFrameLength:最大允许的帧长度,即字节数组的最大长度,包括附加信息、长度表示等内容。如果帧的长度大于此值,将抛出 TooLongFrameException 异常。
  • lengthFieldOffset:长度字段在字节数组中的偏移量。
  • lengthFieldLength:长度字段的字节数。
  • lengthAdjustment:长度字段值需要调整的值。例如,如果长度字段表示的是整个字节数组的长度,但是在传输过程中还包含了一些其他的信息,那么就需要将长度字段的值减去这些额外信息的长度。
  • initialBytesToStrip:解码器在返回帧之前应该跳过的字节数。例如,如果帧包含了长度字段本身的字节,那么这些字节就需要被跳过。

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_后端

解析

接下来,博主将讲解 LengthFieldBasedFrameDecoder 源码中列举的一些例子,并结合应用案例进行讲解,为了方便演示,将使用 EmbeddedChannel 函数进行测试。

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_netty_02

例一:偏移量为 0 且长度字段为 2,不剥离标头

从0开始即为长度字段,长度字段的长度为两个字节,0x000C 就是后面 HELLO, WORLD 的长度表示。

lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 0 (= do not strip header)

BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+

这个例子中,lengthFieldLength = 2 表示长度字段所占的字节数为2,长度字段的值为12(0x0C),它表示 HELLO, WORLD 的长度。默认情况下,解码器假定长度字段表示紧随长度字段后面的字节数量。因此,可以使用简单的参数组合对其进行解码。

测试代码:

ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();  
buffer.writeBytes(new byte[]{0x00, 0x0c});  
buffer.writeBytes("HELLO, WORLD".getBytes());  
channel.writeInbound(buffer);

运行结果:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_netty_03

例二:偏移量为 0 且长度字段为 2,剥离标头

从0开始即为长度字段,长度字段的长度为两个字节,但是读取时从第3个字节开始读取,即跳过长度字段,直接读取内容 HELLO, WORLD

lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 2 (= the length of the Length field)

BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
+--------+----------------+      +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
+--------+----------------+      +----------------+

如果需要通过调用 ByteBuf.readableBytes() 获取内容长度,那么可以通过指定 initialBytesToStrip 来剥离长度字段。在这个例子中,指定了 initialBytesToStrip = 2,这与长度字段的长度相同,可以剥离前两个字节。

测试代码与例一相同,运行结果:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_后端_04


例三:偏移量为 0 且长度字段为 2,不剥离标头,长度字段代表整个消息的长度

从0开始即为长度字段,长度字段的长度为两个字节,0x000E 表示长度字段的长度与内容 HELLO, WORLD 的长度总和,即 2 + 12 = 14 = 0x0E。

lengthFieldOffset   =  0
lengthFieldLength   =  2
lengthAdjustment    = -2 (= the length of the Length field)
initialBytesToStrip =  0

BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+

在大多数情况下,长度字段仅表示消息正文的长度,就像前面的例子所示。然而,在某些协议中,长度字段表示整个消息(包括消息头)的长度。在这种情况下,我们需要指定一个非零的 lengthAdjustment 参数来进行修正。由于这个例子消息中的长度值 0x0E 比正文长度大2,所以我们要指定-2作为 lengthAdjustment 参数来进行补偿。

测试代码:

ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();  
buffer.writeBytes(new byte[]{0x00, 0x0e});  
buffer.writeBytes("HELLO, WORLD".getBytes());  
channel.writeInbound(buffer);

运行结果:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_netty_05


例四:长度字段为 3 且位于长度为 5 的标头末尾,不剥离标头

长度字段前面还有两个字节的其他内容 Header 1 (0xCAFE),第3个字节开始才是长度字段,长度字段为3个字节,并且 Header1 中有附加信息,读取长度字段时需要跳过这些附加信息来获取长度

lengthFieldOffset   = 2 (= the length of Header 1)
lengthFieldLength   = 3
lengthAdjustment    = 0
initialBytesToStrip = 0

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" |
+----------+----------+----------------+      +----------+----------+----------------+

这个例子是第一个示例的简单变体。在消息前面添加了一个额外的标头值。lengthAdjustment 再次为零,因为解码器始终考虑到在帧长度计算期间将预先添加的数据的长度。

测试代码:

ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();  
buffer.writeByte(0xCA);  
buffer.writeByte(0xFE);  
buffer.writeBytes(new byte[]{0x00, 0x00, 0x0c});  
buffer.writeBytes("HELLO, WORLD".getBytes());  
channel.writeInbound(buffer);

运行结果:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_后端_06


例五:长度字段为 3 且位于长度为 5 的标头末尾,剥离标头

从0开始即为长度字段,长度字段的长度为3个字节,长度字段之后还有两个字节的其他内容 0xCAFE0x00000C 表示的是 lengthAdjustment 之后开始的数据的长度,即 HELLO, WORLD,不包括 0xCAFE

lengthFieldOffset   = 0
lengthFieldLength   = 3
lengthAdjustment    = 2 (= the length of Header 1)
initialBytesToStrip = 0

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" |
+----------+----------+----------------+      +----------+----------+----------------+

这个例子是一个高级示例,展示了在长度字段和消息体之间存在额外标头的情况。这里必须指定一个正的 lengthAdjustment 值,以便解码器将额外的标头计入帧长度的计算中。

测试代码:

ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();  
buffer.writeBytes(new byte[]{0x00, 0x00, 0x0c});  
buffer.writeByte(0xCA);  
buffer.writeByte(0xFE);  
buffer.writeBytes("HELLO, WORLD".getBytes());  
channel.writeInbound(buffer);

运行结果:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_java_07


例六:偏移量为 1 且 长度字段为 2 的长度为 4 的标头,去掉第一个头字段和长度字段

长度字段前面有1个字节的其他内容,后面也有1个字节的其他内容,读取时将会忽略3个字节,即 HDR1 + LEN

lengthFieldOffset   = 1 (= the length of HDR1)
lengthFieldLength   = 2
lengthAdjustment    = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)

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,而额外标头影响 lengthAdjustment。我们还指定了非零的 initialBytesToStrip 以从帧中剥离长度字段和前置标头。如果不想剥离前置标头,则可以将 initialBytesToSkip 指定为0。

测试代码:

ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();  
buffer.writeByte(0xCA);  
buffer.writeBytes(new byte[]{0x00, 0x0c});  
buffer.writeByte(0xFE);  
buffer.writeBytes("HELLO, WORLD".getBytes());  
channel.writeInbound(buffer);

运行结果:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_netty_08

initialBytesToSkip = 0 时,运行结果如下所示:

【Netty】「优化进阶」(二)浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割?_后端_09

如果 initialBytesToSkip = 4 时,0xfe 也将不再显示。


后记

总之,通过本文对 LengthFieldBasedFrameDecoder 的深入解析,我们了解了它的工作原理以及如何实现可靠的消息分割。LengthFieldBasedFrameDecoder 可以根据消息长度对网络流进行自动切割,并将每个消息的内容分别处理,从而使得处理网络数据变得更加方便和高效。但是,开发者在使用 LengthFieldBasedFrameDecoder 时需要仔细考虑各种情况,保证其正确性和健壮性。

以上就是 浅谈 LengthFieldBasedFrameDecoder:如何实现可靠的消息分割? 的所有内容了,希望本篇博文对大家有所帮助!

参考:


标签:Netty,浅谈,buffer,字段,WORLD,长度,字节,HELLO,进阶
From: https://blog.51cto.com/sidiot/6703746

相关文章

  • Git小白到老鸟的进阶之路
    小白:师兄,师兄,上次你教我的操作,我傻乎乎的执行了一遍,可是那个Git究竟是什么那?师兄:小白莫慌,Git就是一种版本控制,小白,你平时写论文,是不是也按日期保存成许多的版本那。小白:对呀,对呀。师兄:那开发项目的时候,每个人开发的部分都不一样,需要记录多个版本,这个就是我们伟大的Git做得。小白:师......
  • 文本格式进阶
    day1描述型列表链接 注意的点:一个术语可以同时有多个描述,如:<dl><dt>旁白</dt><dd>戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。</dd><dd>写作中,指与当前主题相关的一段......
  • [第三章 web进阶]Python里的SSRF
    一、运行靶机 发现没有有效界面信息,这个时候查看靶机说明信息,在说明信息里面明确提到访问容器内部的8000端口和urlpath/api/internal/secret即可获取flag 二、根据提示信息访问url由于提示信息中提到urlpath,则可以尝试设置参数名为url?url=http://127.0.0.1:8000/......
  • 浅谈DevOps
    1.DevOps概述1.1定义DevOps(DevelopmentandOperations)是一种软件开发和运维的方法论和实践,旨在通过加强开发团队和运维团队之间的协作和整合,提高软件交付和运维的效率、可靠性和质量。传统上,开发团队负责软件开发、功能实现和变更管理,而运维团队负责部署、配置和维护生产环......
  • SpringBoot中使用Netty开发WebSocket服务-netty-websocket-spring-boot-starter开源项
    场景SpringBoot+Vue整合WebSocket实现前后端消息推送:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/114392573SpringCloud(若依微服务版为例)集成WebSocket实现前后端的消息推送:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/114480731若依前后......
  • HCIP-进阶实验07-高可靠性园区网部署
    HCIP-进阶实验07-高可靠性园区网部署1实验需求1.1实验拓扑1.2实验环境说明设备接口IP地址备注AR1G0/0/0172.16.1.1/24G0/0/1172.16.2.1/24loopback0202.22.2.2/24ISPSW1VLANIF10192.168.1.254/24VLANIF20192.168.2.253/24......
  • 【学习笔记】空空的浅谈DP
    特邀讲师:墨染空洛谷用户@Remakedalao博客中的学习笔记:https://www.cnblogs.com/dmoransky/p/14063918.htmlDP1决策单调性1.2由已知量转移:分治算法洛谷P3515:[POI2011]LightningConductor1.3由之前状态转移:单调栈上二分洛谷P1912:[NOI2009]诗人小G\(f......
  • 【Netty】「源码解析」(三)设置连接超时:深入分析 ChannelFuture.sync() 的执行过程
    前言本篇博文是《从0到1学习Netty》中源码系列的第三篇博文,主要内容是深入分析连接超时的实现原理,包括了connect方法的源码解析和ChannelFuture.sync()执行过程的解析。,往期系列文章请访问博主的Netty专栏,博文中的所有代码全部收集在博主的GitHub仓库中;介绍在实际应用中,当......
  • 浅谈BIT本科数据结构与算法课程 1
    关于C++基本输入输出流#include<bits/stdc++.h>usingnamespacestd;intmain(){ inta,b; cin>>a>>b; cout<<a<<endl; return0;}栈和队列关于stl#include<algorithm>vector<int>x;x.push_back(n);x.pop_back();x.back();x[1......
  • 浅谈如何向上管理
    最近听说了很多事,加之目前自己也处在被汇报以及需要向上汇报的状态中间,迫使我开始思考向上管理(managingup)这个话题。这是一个有争议的话题,很多人(包括曾经的自己)下意识的会将向上管理与徒有其表的讨好或者迎合这类负面词划上等号。借此契机在查阅了很多资料之后,才意识到它不过是一......