本节将重点探讨网络编程中一种非常通用的协议设计方法论:协议头 + 消息体。

所谓的通信协议就是通信双方共同遵循的一种“约定”,用于通信发送方将内容按照“通信协议”所规定的格式组装成**“二进制流”**,通信接收方按照“通信协议”所规定的格式正确的从二进制流中解码出一个个原始请求。

那通信协议如何设计呢?

1、通用的协议设计方法论

在网络编程中,流行着一种经典的协议设计方法论:协议头 + 消息体。

其设计的关键点如下:

协议头的长度是固定的,通常为识别出一个业务的最小长度。

协议头中会包含一个长度字段,用来标识一个完整包的长度,用来表示长度字段的字节位数直接决定了一个包的最大长度,长度字段通常被设计为4个字节。

消息体中存储业务数据,例如如果是一个Dubbo协议,那消息体中可能会包含请求参数、调用的服务名等,而且字符串类的存储通常会采取字段长度、字段内容的组织方式。

为了有一个更直观的展示,我以一个简单的RPC通信场景为例,实现类似Dubbo服务的远程服务调用,其通信协议可以简单设置成下图所示:

基于 Header + Boby 的通信协议设计模式后,通信接收方就能很好的从二进制流中非常容易地解码出一条一条原始的请求数据包,解码的基本套路如下(在面试中面试官非常喜欢问的“粘包”问题的破解之道)

首先判断累积缓存区中是否存在一个完整的Head头部,例如上述示例中,一个包的Header的长度为6个字节,那首先判断累积缓存中可读字节数是否大于等于6,如果不足6个字节,跳过本次处理,等待更多数据到达累积缓存区。

尝试将头部6个字节读取,并且提取长度字段中存储的数值,即包长度,然后判断累积缓存区中可读字节数大于等于整个包的长度,如果累积缓存区不包含一个完整的数据包,则跳过本次处理,等待更多数据到达累积缓存区。

如果包含一个完整的包,则按照通信协议的格式按序读取相关的内容。

正是因为这种设计理念非常通用,Netty 对上述协议设计进行了统一封装:

LengthFieldBasedFrameDecoder 闪亮登场了,接下来我们来看看Netty是如何进行封装的,揭晓更多的实现细节,让大家做到理论与实践相结合。

2、LengthFieldBasedFrameDecoder 详解

2.1 概述

接下来对其核心属性进行一个详细的解读:

ByteOrder byteOrder

字节序列,Netty默认使用大端序列(主要是针对int、long等数值类型),所谓的大端序列,通常可以这样理解,接收端收到的字节流的顺序是从数值类型的高字节。

int maxFrameLength

一条消息最大的长度。

int lengthFieldOffset

代表长度字段的开始偏移量。

int lengthFieldLength

代表长度字段占用的字节长度。

int lengthFieldEndOffset

代表长度字段的结束偏移量,等于lengthFieldOffset + lengthFieldLength。

int lengthAdjustment

长度适配适配值。该值表示协议中长度字段与消息体字段直接的距离。

int initialBytesToStrip

跳过一个包中前面多少个字节不处理,通常是将协议头部跳过,只将消息体中内容传输到下游时使用。

boolean failFast

是否快速失败。

boolean discardingTooLongFrame

是否吞没(跳过)大帧包。

long tooLongFrameLength

当前在处理吞没大包的实际大小。

long bytesToDiscard

下一次解码之前,需要先忽略的字节数,当遇到超过maxFrameLength的包时使用。

上面的属性如果不太好理解,没关系,因为本节的最后会有两张图勾画出协议的全貌(用图示的方式勾画出各个属性的位置与含义) 。

2.2 decode 方法详解

接下来我们来看一下其decode方法,通过阅读源码的方法来理解其内部的工作原理。

LengthFieldBasedFrameDecoder#decode

Step1:跳过无效数据包的处理逻辑。如果discardingTooLongFrame为true,表示正在处理大于****maxFrameLength的包,需要跳过这个超长的包,不对其解码,由于数据是陆续到达累积缓存区,并不能一次跳过整个无效包,故需引入 bytesToDiscard 变量,用于记录本次能跳过的字节,当 bytesToDiscard 为 0后表示一个无效包已全部跳过,需要处理正常数据包,此时discardingTooLongFrame 会重置为 false。

Step2:如果累积缓冲区的可读字节大小小于length字段的结束偏移量,返回null,结束解码,说明该累积缓存区中的数据还不完整。

Step3:尝试从累积缓存区中获取包的长度。其中表示 lengthFiedlOffset 表示长度字段的其实偏移量,在结合长度字段的长度 lengthFieldLength ,再结合字节序列**(大端序列、小端序列)**。

Step4:这里是包长度超过协议允许的最大包长度时的处理逻辑,在这里大家先姑且跳过 lengthAdjustment 属性的含义。

如果当前累积缓存区中的可读字节大于 frameLength,大于当前包的长度,可以通过调用 skipBytes 方法跳过这包。

如果当前累积缓存区的可读自己小于 frmaeLength,需要分多次跳过,故先将累积区中的数据全部跳过,然后通过 bytesToDiscard 记录还需要跳过的字节数。

Step5:如果累积缓存区中的数据不包含一个完整的包,返回null,结束本次解码,等待更多的数据包的到来。

Step6:通过 ByteBuf 的 slince 方法,提取一个完整的包长度,解码出完整的数据包,完成一个数据包解码。

2.3 图解 LengthFieldBasedFrame 协议

在Netty 的

LengthFieldBasedFrameDecoder 中有一个 lengthAdjustment 属性,可以是正数,也可以是负数,其使用的代码片段如下:

frameLength += lengthAdjustment + lengthFieldEndOffset

lengthAdjustment 长度调整字段,可以为正数,也可以为负数,主要的作用是 长度字段中的值是否包含 Header 长度本身,严格意义上来说应该是包含 长度字段之前的字节序列。

1、lengthAdjustment > 0

2、lengthAdjustment < 0

在大多数情况下,length字段表示消息正文的长度,但是有些协议,其长度表示的是整个消息的长度,故Netty为了适配这种情况,可以通过 lengthAdjustment 设置为负数,来调节数据帧的大小。

总结:lengthAdjustment 的出现是Netty为了适配现有的协议而设计出来的字段,即 Netty

LengthFieldBasedFrameDecoder 是为了i给 header + body ,并且基于长度字段的协议一种通用的解决方案,可以通过 lengthAdjustment 来准确表示数据帧(业务数据的长度),这里是一种逆向思维。

3、协议设计子类的最佳实践

最佳实践:

LengthFieldBasedFrameDecoder 的 decode 方法的职责是从二进制流中解码出一个完整的数据包,其返回类型还是 ByteBuf,故自定义的编码解码器的 decode 方法就是先调用父类的 decode 方法 得到 ByteBuf ,然后对 ByteBuf 中的数据解码出对象。

LengthFieldBasedFrameDecoder 并不负责将 ByteBuf 转换为协议对象,而是从二进制流中解码出一个数据帧,而将ByteBuf 转换为协议对象的职责由其子类实现,通常的编码风格如下:

资源获取:
大家 点赞、收藏、关注、评论啦 、 查看

揭秘通信协议设计的奥妙,作为面试官我都看蒙了相关推荐

  1. 那些面试官,都是如何把候选人聊崩溃的

    <兄弟,你太用力了>分享了面试官关注的一些点,帮助候选人规避面试过程中的一些坑. 今天,站在候选人的角度,聊聊面试,看能否帮助面试官规避面试过程中的一些坑. 画外音:探测候选人深浅,也是一 ...

  2. 关于Transformer,面试官们都怎么问?

    作者 | Adherer 编辑 | NewBeeNLP 面试锦囊之知识整理系列,持续更新中 已发表: 关于ELMo,面试官们都怎么问 写在前面 前些时间,赶完论文,开始对 Transformer.GP ...

  3. 关于逻辑回归,面试官们都怎么问

    作者 | WEIWEI   整理 | NewBeeNLP 「面试官们都怎么问」系列文章主旨是尽可能完整全面地整理ML/DL/NLP相关知识点,不管是刚入门的新手.准备面试的同学或是温故知新的前辈,我们 ...

  4. 面试官到底想看什么样的简历?三分钟教你拿下面试机会

    面试一直是程序员跳槽时期非常热门的话题,虽然现在已经过了跳槽的旺季,下一轮跳槽季需要到年底才会出现,但是当跳槽季的时候你再看这篇文章可能已经晚了,过冬的粮食永远不是冬天准备的,而是秋收的时候. 一.简 ...

  5. 站在面试官角度,看求职与内卷

    最近团队有人员变动,然后就顺理成章的开始招聘事项,下面作者尽量从流程全貌和大家总结一下面试过程,最近好多读者都在和我私聊说年底跳槽的事,也正好可以提供另一个角度做参考. 一.招聘需求 互联网的行业的内 ...

  6. 字节跳动面试官问我看过哪些源码,然后就没有然后了

    最近,我的一位朋友在找工作,已经拿到了美团.快手等公司的Offer,准备选择其中一家入职了. 后来他又接到了字节跳动的电话,通知他去参加三面.从二面到三面之间隔了挺久的,他以为都没戏了,结果就收到了通 ...

  7. jqgrid为什么表头和数据之间有间隙_面试官:你看过Redis数据结构底层实现吗?...

    面试中,redis也是很受面试官亲睐的一部分.我向在这里讲的是redis的底层数据结构,而不是你理解的五大数据结构.你有没有想过redis底层是怎样的数据结构呢,他们和我们java中的HashMap. ...

  8. golang实现的布隆过滤器_面试官:都 2020 年,你在干嘛?还不知道布隆过滤器

    关注过 @Python大星 的小伙伴应该知道,2020 年 4 月 Python 小星最近裸面了阿里巴巴菜鸟网络科技有限公司. 一面中面试官非常重视解决 Redis 缓存穿透问题的利器--布隆过滤器, ...

  9. springboot 手动提交事务_面试官你都工作3年了,也做过5个项目了,怎么连事务机制都不会...

    分享职场生活.职场攻略.程序员创业资源,为一线开发者提供优质内容 张工是一名java程序员,最近到某互联网公司面试,面试官问了这样一个问题: 有这样一个场景,需要往订单主表和明细表插入数据,如何保证订 ...

  10. 简历有6个月空白期,面试官会怎么看?

    网友"大嘴巴鸭"提问: 去年12月辞职,到今年6月不是说没找到工作,而是做过5份工作了,都是做了1个月左右不是自离就是被炒.经历了这么多累了,只想找份稳定的工作!可是简历上我写的只 ...

最新文章

  1. TensorFlow 损失函数
  2. 物联网将迎丛林时代 细数物联网八大发展模式
  3. 【图论】最短路上的统计(ssl 1500)
  4. 销毁AWS资源:Cloud-Nuke还是AWS-Nuke?
  5. 【Python】Python库之文本处理
  6. 餐饮小票打印app_POS收银工业平板电脑在餐饮行业的应用
  7. matlab figure 窗口最大化
  8. javascript对行单击事件处理(委托事件)
  9. AM5728 IPC机制解析
  10. 网易云音乐地址解析含官方接口分享
  11. ADX趋势线突破交易系统
  12. 【统计学】三大相关系数之肯德尔相关系数(kendall correlation coefficient)
  13. .bss段和.data段引起的文件大小增加
  14. c语言程序填空题库,c语言填空题题库
  15. 神经网络辨识的优势是,神经网络辨识的优势有
  16. python短信验证码_python发送短信验证码
  17. 【Pycharm】主题背景颜色更改
  18. RK3326 android10.0(Q) 系统精简瘦身
  19. 基于Quartus-FPGA制作蜂鸣器的相关教程
  20. 从标数法求最短路径数到杨辉三角的思考

热门文章

  1. 手持终端提升护士工作效率
  2. 零伽壹浅谈:如何利用区块链解决中小企业“融资难融资贵”的问题
  3. socketChatRoom_Server(聊天室服务端)
  4. 视频压缩与编解码的基本原理
  5. xxx酒业有限责任公司突发环境事件应急预案WORD
  6. 计算机毕设Python+Vue学生健康饮食信息管理系统(程序+LW+部署)
  7. PopupWindow 引起所依附的Activity透明
  8. 关于DEM相关算法设计——DEM的数据设计
  9. 名人商标转让有几类?
  10. 错误:on path: DexPathList[[zip file /data/app