Netty源码分析第6章(解码器)---->第4节: 分隔符解码器

Netty源码分析第六章: 解码器

第四节: 分隔符解码器

基于分隔符解码器DelimiterBasedFrameDecoder, 是按照指定分隔符进行解码的解码器, 通过分隔符, 可以将二进制流拆分成完整的数据包

同样继承了ByteToMessageDecoder并重写了decode方法

我们看其中的一个构造方法:

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

这里参数maxFrameLength代表最大长度, delimiters是个可变参数, 可以说可以支持多个分隔符进行解码

我们进入decode方法:

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; //找到最小长度的分隔符(2)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 {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解码器

这个成员变量, 会在分隔符是\n和\r\n的时候进行初始化

我们看初始化该属性的构造方法:

public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {//代码省略//如果是基于行的分隔if (isLineBased(delimiters) && !isSubclass()) {//初始化行处理器lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);this.delimiters = null;} else {//代码省略
    }//代码省略
}

这里isLineBased(delimiters)会判断是否是基于行的分隔, 跟到isLineBased方法中:

private static boolean isLineBased(final ByteBuf[] delimiters) {//分隔符长度不为2if (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

然后拿到第一个分隔符a和第二个分隔符b, 然后判断a的第一个分隔符是不是\r, a的第二个分隔符是不是\n, b的第一个分隔符是不是\n, 如果都为true, 则条件成立

我们回到decode方法中, 看步骤2, 找到最小长度的分隔符:

这里最小长度的分隔符, 意思就是从读指针开始, 找到最近的分隔符

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

这里会遍历所有的分隔符, 然后找到每个分隔符到读指针到数据包长度

然后通过if判断, 找到长度最小的数据包的长度, 然后保存当前数据包的的分隔符, 如下图:

6-4-1

这里假设A和B同为分隔符, A分隔符到读指针的长度小于B分隔符到读指针的长度, 这里会找到最小的分隔符A, 分隔符的最小长度, 就readIndex到A的长度

我们继续看第3步, 解码:

if (minDelim != null) 表示已经找到最小长度分隔符, 我们继续看if块中的逻辑:

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;

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块:

if (!discardingTooLongFrame) { if (buffer.readableBytes() > maxFrameLength) { tooLongFrameLength = buffer.readableBytes();buffer.skipBytes(buffer.readableBytes());discardingTooLongFrame = true;if (failFast) {fail(tooLongFrameLength);}}
} else {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()) 将所有的字节继续进行丢弃

以上就是分隔符解码器的相关逻辑

第六章总结

本章介绍了抽象解码器ByteToMessageDecoder, 和其他几个实现了ByteToMessageDecoder类的解码器, 这个几个解码器逻辑都比较简单, 同学们可以根据其中的思想剖析其他的比较复杂的解码器, 或者根据其规则实现自己的自定义解码器

上一节: 行解码器

下一节: writeAndlush事件传播

posted on 2019-01-01 22:56 向南是个万人迷 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/xiangnan6122/p/10206475.html

Netty源码分析第6章(解码器)----第4节: 分隔符解码器相关推荐

  1. Netty源码分析第5章(ByteBuf)----第5节: directArena分配缓冲区概述

    Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述 Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上 ...

  2. Netty源码分析第7章(编码器和写数据)----第2节: MessageToByteEncoder

    Netty源码分析第7章(编码器和写数据)---->第2节: MessageToByteEncoder Netty源码分析第七章: Netty源码分析 第二节: MessageToByteEnc ...

  3. Netty源码分析第1章(Netty启动流程)----第4节: 注册多路复用

    Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用 Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channe ...

  4. Netty源码分析系列之常用解码器(下)——LengthFieldBasedFrameDecoder

    扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,Spring源码分析和Java并发编程文章. 前言 在上一篇文章中分析了三个比较简单的解码器,今天接着分析最后一个常用的解码器:Leng ...

  5. 【Netty源码分析摘录】(八)新连接的接入

    文章目录 1.问题 2.检测新连接接入 3.创建客户端 channel 4. 绑定 NioEventLoop 4.1 register0 4.1.1 doRegister() 4.1.2 pipeli ...

  6. Netty源码分析(六)—Future和Promis分析

    Netty源码分析(六)-Future和Promis分析 Future用来在异步执行中获取提前执行的结果 个人主页:tuzhenyu's page 原文地址:Netty源码分析(六)-Future和P ...

  7. Netty源码分析系列之服务端Channel的端口绑定

    扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,Spring源码分析和Java并发编程文章. 微信公众号 问题 本文内容是接着前两篇文章写的,有兴趣的朋友可以先去阅读下两篇文章: Ne ...

  8. Netty 源码分析系列(十五)自定义解码器、编码器、编解码器

    前言 我们今天继续来分析 Netty 的编解码器,这次我们要自己动手实现自定义的编码器.解码器和编解码器. 自定义基于换行的解码器 LineBasedFrameDecoder 类 LineBasedF ...

  9. netty源码分析系列——EventLoop

    2019独角兽企业重金招聘Python工程师标准>>> 前言 EventLoop也是netty作为一个事件驱动架构的网络框架的重要组成部分,netty主要通过它来实现异步编程,从前面 ...

最新文章

  1. es父子结构查询_ES 父子文档查询
  2. 在局域网访问_管理Windows访问凭证,快速访问局域网上的共享资源
  3. Accessing tools within a VB6 program - AE中使用VB调用Geoprocessing
  4. div+css控制最小高度又自适高度
  5. php、linux、javascript 正则表达式
  6. 电视剧中提到的 自然的 聊天对话 细节法则
  7. java实现条件编译
  8. c++图形化界面_还能这样用?Linux下如何编译C程序?
  9. 为你的电脑系统清除淤塞的垃圾!(不用任何软件,超过优化大师)
  10. Struts2【一】 配置介绍
  11. 从浏览器中下载文件如何修改默认保存位置
  12. ICCV2021 MuST:还在特定任务里为刷点而苦苦挣扎?谷歌的大佬们都已经开始玩多任务训练了...
  13. 2021-06-01 深入分析锁升级流程的基础
  14. 关于scanf 函数,你很少了解的“秘密”
  15. 为什么越来越多的人喜欢旅游?
  16. ubuntu mysql5.5编码_Ubuntu下MySQL5.5编码设置
  17. 谭浩强c语言入门prd,完整C语言谭浩强学习笔记.docx
  18. PyQt设置右下角弹窗
  19. 述职答辩提问环节一般可以问些什么_述职提问环节应该提哪些关键问题?
  20. IDEA “Cannot resolve symbol” 解决办法

热门文章

  1. python多久学会自学-python自学多久
  2. python中国大学排名爬虫写明详细步骤-python中国大学排名爬虫
  3. python绘制3d图-Python matplotlib绘图示例 - 绘制三维图形
  4. python简单编程例子-中文方便就用中文编程!Python图形界面开发实例
  5. 自学python都需要哪些书-【经验分享】自学Python的学习顺序!附学习资料
  6. python第一次使用教程-python入门教程第一日
  7. 成都python培训比较好的机构-成都Python培训班哪个好,怎样才能不走弯路学习
  8. c与python的区别-c语言和python的区别是什么
  9. python基础知识资料-python基础知识整理(值得收藏)
  10. python打开一个文件-python下几种打开文件的方式