Netty学习——高级篇2 Netty解码技术 备份

news2024/11/26 21:36:06

接上篇:Netty学习——高级篇1 拆包 、粘包与编解码技术,本章继续介绍Netty的其他解码器

1 DelimiterBasedFrameDecoder分隔符解码器

        DelimiterBasedFrameDecoder 分隔符解码器是按照指定分隔符进行解码的解码器,通过分隔符可以将二进制流拆分成完整的数据包。回车换行符解码器实际上是一种特殊的DelimiterBasedFrameDecoder解码器。

        分隔符解码器在实际工作中有很广泛的应用,很多简单的文本私有协议都以特殊的分隔符作为消息结束的标识,特别是那些使用长连接的基于文本的私有协议。关于分隔符的指定,并非以Char或者String作为构造参数,而是以Bytebuf。下面就结合实际例子给出它的用法。例如消息是以“$_”作为分隔符,服务端或者客户端初始化ChannelPipeline的代码实例如下:

ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
ch.pipeline().addLast(new StringDecoder());

        DelimiterBasedFrameDecoder同样继承了ByteToMessageDecoder并重写了decode方法,来看其中的一个构造方法。

public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {
    this(maxFrameLength, true, delimiter);
}

         其中,参数 maxFrameLength 代表最大长度,delimiter是个可变参数,可以支持多个分隔符进行解码。跟进decode方法。

@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    Object decoded = decode(ctx, in);
    if (decoded != null) {
        out.add(decoded);
    }
}

             

        这里同样调用了重载的decode方法并将解析好的数据添加到集合List中,其父类就可以遍历Out,并将内容传播。重载的decode方法代码如下:

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
        //行处理器(1)  
        if (lineBasedDecoder != null) {
            return lineBasedDecoder.decode(ctx, buffer);
        }
        
        int minFrameLength = Integer.MAX_VALUE;
        ByteBuf minDelim = null;
        //找到最小长度的分隔符
        for (ByteBuf delim: delimiters) {
            //每个分隔符分隔的数据包长度
            int frameLength = indexOf(buffer, delim);
            if (frameLength >= 0 && frameLength < minFrameLength) {
                minFrameLength = frameLength;
                minDelim = delim;
            }
        }
        //解码(3)
        //已经找到分隔符
        if (minDelim != null) {
            int minDelimLength = minDelim.capacity();
            ByteBuf frame;
            //当前分隔符是否处于丢弃模式
            if (discardingTooLongFrame) {
                // 首先设置为非丢弃模式
                discardingTooLongFrame = false;
                //丢弃
                buffer.skipBytes(minFrameLength + minDelimLength);

                int tooLongFrameLength = this.tooLongFrameLength;
                this.tooLongFrameLength = 0;
                if (!failFast) {
                    fail(tooLongFrameLength);
                }
                return null;
            }
            //处于非丢弃模式
            //当前找到的数据包,大于允许的数据包
            if (minFrameLength > maxFrameLength) {
                // 当前数据包+最小分隔符的长度 全部丢弃.
                buffer.skipBytes(minFrameLength + minDelimLength);
                //传递异常事件
                fail(minFrameLength);
                return null;
            }
            //如果是正常的长度
            //解析出来的数据包是否忽略分隔符
            if (stripDelimiter) {
                //如果不包含分隔符
                //截取
                frame = buffer.readRetainedSlice(minFrameLength);
                //跳过分隔符
                buffer.skipBytes(minDelimLength);
            } else {
                //截取包含分隔符的长度
                frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
            }

            return frame;
        } else {
            //如果没有找到分隔符
            //飞丢弃模式
            if (!discardingTooLongFrame) {
                //可读字节大于允许的解析出来的长度
                if (buffer.readableBytes() > maxFrameLength) {
                    // 将这个长度记录下
                    tooLongFrameLength = buffer.readableBytes();
                    //跳过这段长度
                    buffer.skipBytes(buffer.readableBytes());
                    //标记当前处于丢弃状态
                    discardingTooLongFrame = true;
                    if (failFast) {
                        fail(tooLongFrameLength);
                    }
                }
            } else {
                // Still discarding the buffer since a delimiter is not found.
                tooLongFrameLength += buffer.readableBytes();
                buffer.skipBytes(buffer.readableBytes());
            }
            return null;
        }
    }

        这个方法较长,通过拆分进行剖析:

        1、行处理器;2、找到最小长度分隔符;3、解码。首先看第一步,行处理器的代码:

if (lineBasedDecoder != null) {
    return lineBasedDecoder.decode(ctx, buffer);
}

        首先判断成员变量linebasedDecoder 是否为空,如果不为空则直接调用lineBasedDecoder的decode方法进行解析,lineBasedDecoder实际上就是LineBasedFrameDecoder解码器。这个成员变量会在分隔符是\r\n的时候进行初始化。看一下初始化该属性的构造方法:

public DelimiterBasedFrameDecoder(
        int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {
    validateMaxFrameLength(maxFrameLength);
    if (delimiters == null) {
        throw new NullPointerException("delimiters");
    }
    if (delimiters.length == 0) {
        throw new IllegalArgumentException("empty delimiters");
    }
    //如果是基于行的分隔
    if (isLineBased(delimiters) && !isSubclass()) {
        //初始化行处理器
        lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
        this.delimiters = null;
    } else {
        this.delimiters = new ByteBuf[delimiters.length];
        for (int i = 0; i < delimiters.length; i ++) {
            ByteBuf d = delimiters[i];
            validateDelimiter(d);
            this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
        }
        lineBasedDecoder = null;
    }
    this.maxFrameLength = maxFrameLength;
    this.stripDelimiter = stripDelimiter;
    this.failFast = failFast;
}

        这里的 isLineBased(delimiters) 会判断是否是基于行的分隔,代码如下:

private static boolean isLineBased(final ByteBuf[] delimiters) {
    //分隔符长度不为2
    if (delimiters.length != 2) {
        return false;
    }
    //获得第一个分隔符
    ByteBuf a = delimiters[0];
    //获得第二个分隔符
    ByteBuf b = delimiters[1];
    if (a.capacity() < b.capacity()) {
        a = delimiters[1];
        b = delimiters[0];
    }
    //确保a是\r\n分隔符,确保b是\n分隔符
    return a.capacity() == 2 && b.capacity() == 1
            && a.getByte(0) == '\r' && a.getByte(1) == '\n'
            && b.getByte(0) == '\n';
}

        首先判断长度不等于2,直接返回false。然后获取第一个ByteBuf和第二个ByteBuf,判断a的第一个分隔符是不是\r,a第二个分隔符是不是\n,b的第一个分隔符是不是\n,如果都为true,则条件成立。回到decode中,看第二步,找到最小长度的分隔符。这里最小长度的分隔符,就是从读指针开始找到最近的分隔符,代码如下:

for (ByteBuf delim: delimiters) {
    //每个分隔符分隔的数据包长度
    int frameLength = indexOf(buffer, delim);
    if (frameLength >= 0 && frameLength < minFrameLength) {
        minFrameLength = frameLength;
        minDelim = delim;
    }
}

        这里会遍历所有的分隔符,找到每个分隔符到读指针的数据包长度。在通过if判断,找到长度最小的数据包的长度,然后保存当前数据包的分隔符,如下图所示:

        假设A和B同为分隔符,因为A分隔符到读指针的长度小于B分隔符到读指针的长度,所以会找到最小的分隔符A,分隔符的最小长度就是readIndex到A的长度。继续看第三步,解码。

if (minDelim != null) 表示已经找到最小长度分隔符,继续看if语句块中的逻辑,代码如下:
int minDelimLength = minDelim.capacity();
ByteBuf frame;

if (discardingTooLongFrame) {
    // We've just finished discarding a very large frame.
    // Go back to the initial state.
    discardingTooLongFrame = false;
    buffer.skipBytes(minFrameLength + minDelimLength);

    int tooLongFrameLength = this.tooLongFrameLength;
    this.tooLongFrameLength = 0;
    if (!failFast) {
        fail(tooLongFrameLength);
    }
    return null;
}

if (minFrameLength > maxFrameLength) {
    // Discard read frame.
    buffer.skipBytes(minFrameLength + minDelimLength);
    fail(minFrameLength);
    return null;
}

if (stripDelimiter) {
    frame = buffer.readRetainedSlice(minFrameLength);
    buffer.skipBytes(minDelimLength);
} else {
    frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
}

return frame;

         if (discardingTooLongFrame) 表示当前是否处于非丢弃模式,如果是丢弃模式,则进入if块中。因为第一个不是丢弃哦是,所以先分析if后面的逻辑。if (minFrameLength > maxFrameLength) 判断当前找到的数据包长度大于最大长度,这个最大长度是创建解码器的时候设置的,如果超过了最大长度,就通过 buffer.skipBytes(minFrameLength + minDelimLength);方式跳过数据包+分隔符的长度,也就是将这部分数据进行完全丢弃。继续往下看,如果长度不大于最大允许长度,则通过 if (stripDelimiter) 判断解析出来的数据包是否包含分隔符,如果不包含分隔符,则截取数据包的长度后,跳过分隔符。

        再回看 if (discardingTooLongFrame)中if里的逻辑,也就是丢弃模式。首先将discardingTooLongFrame 设置为false,标记为飞丢弃模式,然后通过buffer.skipBytes(minFrameLength + minDelimLength) 将数据包+分隔符长度的字节数跳过,也就是进行丢弃,之后抛出异常。在分析没有找到分隔符的逻辑处理,也就是if (minDelim != null) 的else语句,代码如下:

else {
    if (!discardingTooLongFrame) {
        if (buffer.readableBytes() > maxFrameLength) {
            // Discard the content of the buffer until a delimiter is found.
            tooLongFrameLength = buffer.readableBytes();
            buffer.skipBytes(buffer.readableBytes());
            discardingTooLongFrame = true;
            if (failFast) {
                fail(tooLongFrameLength);
            }
        }
    } else {
        // Still discarding the buffer since a delimiter is not found.
        tooLongFrameLength += buffer.readableBytes();
        buffer.skipBytes(buffer.readableBytes());
    }
    return null;
}

        首先通过  if (!discardingTooLongFrame) 判断是否为非丢弃模式,如果是非丢弃模式,则进入if里。在if里,通过 if (buffer.readableBytes() > maxFrameLength) 判断当前可读字节数是否大于最大允许的长度,如果大于最大允许的长度,则将可读字节数设置到tooLongFrameLength 的属性中,代表丢弃的字节数,然后通过 buffer.skipBytes(buffer.readableBytes()) 将累加器中所有的可读字节进行丢弃,最后将 discardingTooLongFrame 设置为true,也就是丢弃模式,之后抛出异常。如果 if (!discardingTooLongFrame) 结果为false,也就是当前处于丢弃模式,则追加tooLongFrameLength 也就是丢弃的字节数的长度,并通过buffer.skipBytes(buffer.readableBytes()) 将所有的字节继续进行丢弃。

2 FixedLengthFrameDecoder固定长度解码器

        FixedLengthFrameDecoder 固定长度解码器能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包和拆包等问题,非常实用。

        对于定长消息,如果消息实际长度小于定长,则往往会进行补位操作,它在一定程度长导致了空间和资源的浪费。但是它的优先也非常明显,编解码比较简单,因此在实际项目中有一定的应用场景。

        利用FixedLengthFrameDecoder 解码器,无论一次接收到多少数据报文,它都会按照构造函数中设置的固定长度进行解码,如果是半包消息,FixedLengthFrameDecoder会缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。假如单条消息的长度是20字节,使用FixedLengthFrameDecoder 解码器的效果如下:

        类的定义代码如下:

public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
    
    //长度大小
    private final int frameLength;

    public FixedLengthFrameDecoder(int frameLength) {
        if (frameLength <= 0) {
            throw new IllegalArgumentException(
                    "frameLength must be a positive integer: " + frameLength);
        }
        //保存当前的frameLength
        this.frameLength = frameLength;
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //通过ByteBuf解码,解码到对象之后添加到Out上
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    
    protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //字节是否小于这个固定长度
        if (in.readableBytes() < frameLength) {
            return null;
        } else {
            return in.readRetainedSlice(frameLength);
        }
    }
}

        通过代码发现,FixedLengthFrameDecoder继续ByteToMessageDecoder,重写了decode方法,这个类只有一个叫 frameLength 的属性,并在构造方法中初始化了该属性。再看decode方法,在decode方法中又调用了自身另一个重载的decode方法进行解析,解析出来之后将解析后的数据放在集合Out中。再看重载的decode方法,重载的decode方法中首先判断累加器的字节数是否小于固定长度,如果小于固定长度则返回null,代表不是一个完整的数据包。如果大于等于固定长度,则直接从累加器中截取这个长度的数值,in.readRetainedSlice(frameLength) 会返回一个新的截取后的ByteBuf,并将原来的累加器读指针后移frameLength个字节。如果累加器中还有数据,则通过ByteToMessageDecoder中callDecode()方法里的while循环方式,继续进行解码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1582426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将扁平数据转换为树形数据的方法

当遇到了好多扁平数据我们都无从下手&#xff1f;不知道如何处理&#xff1f; 家人们 无脑调用这个函数就好了 接口请求回来以后 调用这个函数传入实参就可以用啦~ // 树形菜单函数 function GetTreeData(data) {let TreeData [];let map new Map(); //存在id,对应所在的内…

大厂Java笔试题之判断字母大小写

/*** 题目&#xff1a;如果一个由字母组成的字符串&#xff0c;首字母是大写&#xff0c;那么就统计该字符串中大写字母的数量&#xff0c;并输出该字符串中所有的大写字母。否则&#xff0c;就输出* 该字符串不是首字母大写*/ public class Demo2 {public static void main(St…

功能测试_验证某城市电话号码的正确性

案例&#xff1a;验证某城市电话号码的正确性 功能测试_等价类设计用例&#xff1a; 步骤&#xff1a; 1:明确需求&#xff1a;电话号码是否正确 2:划分等价类&#xff1a;有效等价类、有效取值、无效等价类、无效取值 3&#xff1a;提取数据编写用例&#xff1a;用例编号…

4.9QT

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#xf…

vite+react+ts+scss 创建项目

npm create vitelatest输入项目名称选择react选择typescript swc WC 通过利用 Rust 编写的编译器&#xff0c;使用了更先进的优化技术&#xff0c;使得它在处理 TypeScript 代码时能够更快地进行转换和编译。特别是在大型项目中&#xff0c;SWC 相对于传统的 TypeScript 编译器…

液冷是大模型对算力需求的必然选择?|英伟达 GTC 2024六大亮点

在这个以高性能计算和大模型推动未来通用人工智能时代&#xff0c;算力已成为科技发展的隐形支柱。本文将重点探讨算力的演进&#xff0c;深入分析在不同领域中算力如何成为推动进步的基石&#xff1b;着眼于液冷如何突破算力瓶颈成为引领未来的先锋&#xff0c;对液冷散热的三…

探索RAG:加强问答能力的新技术

文章目录 1. RAG是什么&#xff1f;1.1 技术简介1.2 挑战与解决方案1.3 RAG技术构成1.4 应用与前景 2. RAG架构详解2.1 典型的RAG应用有两个主要组件2.2 从原始数据到答案的完整流程 3. RAG在实际应用中的案例 1. RAG是什么&#xff1f; 1.1 技术简介 Retrieval Augmented Ge…

设计模式学习笔记 - 设计模式与范式 -行为型:9.迭代器模式(上):相比直接遍历集合数据,使用迭代器模式有哪些优势?

概述 上篇文章&#xff0c;我们学习了状态模式。状态模式是状态机的一种实现方式。它通过将事件触发的状态转移和动作执行&#xff0c;拆分到不同的状态类中&#xff0c;以此来避免状态机类中的分支判断逻辑&#xff0c;应对状态机类代码的复杂性。 本章&#xff0c;学习另外…

【千帆平台】百度智能云千帆AppBuilder应用探索益智游戏之猜物小游戏

欢迎来到《小5讲堂》 这是《千帆平台》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景AppBuilder控制台创建应用设置应用自动配置随机生成AI生成应…

Elasticsearch 嵌套类型的深度剖析与实例

文章目录 **嵌套类型的原理与特点****嵌套类型的创建与映射定义****嵌套查询与过滤****嵌套聚合****实战应用举例** Elasticsearch 嵌套类型的实例 Elasticsearch 索引中的嵌套类型&#xff08;Nested Types&#xff09;是处理具有层次结构或一对多关系数据的有效工具。它允许在…

电工技术学习笔记——正弦交流电路

一、正弦交流电路 1. 正弦量的向量表示法 向量表示方法&#xff1a;正弦交流电路中&#xff0c;相量表示法是一种常用的方法&#xff0c;用于描述电压、电流及其相位关系。相量表示法将正弦交流信号表示为复数&#xff0c;通过复数的运算来描述电路中各种参数的相互关系 …

墨迹天气联合TopOn搭建创新合作模式,深挖广告流量价值 | TopOn变现案例

日前&#xff0c;墨迹天气与移动广告聚合管理平台TopOn达成合作&#xff0c;开发创新思路&#xff0c;通过搭建高效的合作模式&#xff0c;提升商业化效果广告效率和业务水平&#xff0c;共建新场景。 墨迹天气相关负责人表示&#xff0c;作为国内最早布局天气类应用的厂商之一…

python开发poc,fofa爬虫批量化扫洞

学习使用python做到批量化的漏洞脚本 1.通过fofa搜索结果来采集脚本 2.批量化扫描漏洞 ---glassfish存在任意文件读取在默认48484端口&#xff0c;漏洞验证的poc为: "glassfish" && port"4848" && country"CN" http://loca…

工厂水电能耗监测管理云平台

在当今工业生产中&#xff0c;对水电等能源的有效管控已成为企业降低成本、提升竞争力的关键所在。随着云计算、大数据以及物联网技术的飞速发展&#xff0c;工厂水电能耗监测管理云平台作为一种新型的能源管理解决方案&#xff0c;正受到越来越多企业的青睐。该平台通过云技术…

二维相位解包理论算法和软件【全文翻译- DCT相位解包裹(5.3.2)】

5.3.2 基于 DCT 的方法 在本节中,我们将详细介绍如何通过 DCT 算法解决非加权最小二乘相位解缠问题,而不是通过FFT.我们将使用公式 5.53 所定义的二维余弦变换。我们开发的算法等同于 FFT 方法 2(第 5.3.1 节)。与 FFT 方法 I 等价的 DCT 算法也可以推导出来,但我们将其作…

在Graphcore IPU上加速和扩展时态图网络

Graphcore Bow IPU机器。 一、说明 IPU 是一种全新的大规模并行处理器&#xff0c;与Poplar SDK共同设计&#xff0c;旨在加速机器智能。自第一代 Colossus IPU 以来&#xff0c;我们在芯片和系统架构中的计算、通信和内存方面取得了突破性进展&#xff0c;与 MK1 IPU 相比&…

python批量修改替换cad图纸文本,土木狗可以有

civilpy&#xff1a;python进行AutoCAD绘图的两个库&#xff0c;土木狗可以有3 赞同 0 评论文章​编辑 civilpy&#xff1a;python进行AutoCAD绘图批量打印&#xff0c;土木狗可以有2 赞同 2 评论文章​编辑 # 导入所需库 from pyautocad import Autocad, APoint import ma…

探索 PostgreSQL 的高级数据类型 - 第2部分

Navicat for PostgreSQL 是一套专为PostgreSQL设计的强大数据库管理及开发工具。它可以在PostgreSQL数据库7.5以上的版本中运行&#xff0c;并且支持大部份最新的PostgreSQL功能&#xff0c;包括触发器、函数检索及权限管理等。Navicat的的功能不仅可以满足专业开发人员的所有需…

ElasticSearch入门到掌握(3)完结

文章目录 三、深入 elasticsearch1.数据聚合&#xff08;1&#xff09;聚合的分类&#xff08;2&#xff09;DSL 实现 Bucket 聚合&#xff08;3&#xff09;DSL 实现 Metrics 聚合&#xff08;4&#xff09;RestAPI 实现聚合 2.自动补全&#xff08;1&#xff09;自定义分词器…

学习笔记:解决拖延

1 解决拖延、减轻压力的关键心态和方法 1.1 要点梳理 拖延是因为自己一直在逃避&#xff0c;重点是要有效突破逃避圈&#xff0c;进入学习圈&#xff0c;扩展成长圈。 毒蛇曲线&#xff08;见思维导图&#xff09;中越是临近截止期限&#xff0c;拖延的焦虑越上升&#xff0…