【转】netty源码分析之LengthFieldBasedFrameDecoder
原文:https://www.jianshu.com/p/a0a51fd79f62
拆包的原理
关于拆包原理的上一篇博文 netty源码分析之拆包器的奥秘 中已详细阐述,这里简单总结下:netty的拆包过程和自己写手工拆包并没有什么不同,都是将字节累加到一个容器里面,判断当前累加的字节数据是否达到了一个包的大小,达到一个包大小就拆开,进而传递到上层业务解码handler
之所以netty的拆包能做到如此强大,就是因为netty将具体如何拆包抽象出一个decode
方法,不同的拆包器实现不同的decode
方法,就能实现不同协议的拆包
这篇文章中要讲的就是通用拆包器LengthFieldBasedFrameDecoder
,如果你还在自己实现人肉拆包,不妨了解一下这个强大的拆包器,因为几乎所有和长度相关的二进制协议都可以通过TA来实现,下面我们先看看他有哪些用法
LengthFieldBasedFrameDecoder 的用法
1.基于长度的拆包

上面这类数据包协议比较常见的,前面几个字节表示数据包的长度(不包括长度域),后面是具体的数据。拆完之后数据包是一个完整的带有长度域的数据包(之后即可传递到应用层解码器进行解码),创建一个如下方式的LengthFieldBasedFrameDecoder
即可实现这类协议
new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 4);
其中
1.第一个参数是 maxFrameLength
表示的是包的最大长度,超出包的最大长度netty将会做一些特殊处理,后面会讲到
2.第二个参数指的是长度域的偏移量lengthFieldOffset
,在这里是0,表示无偏移
3.第三个参数指的是长度域长度lengthFieldLength
,这里是4,表示长度域的长度为4
2.基于长度的截断拆包
如果我们的应用层解码器不需要使用到长度字段,那么我们希望netty拆完包之后,是这个样子

长度域被截掉,我们只需要指定另外一个参数就可以实现,这个参数叫做 initialBytesToStrip
,表示netty拿到一个完整的数据包之后向业务解码器传递之前,应该跳过多少字节
new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 4, 0, 4);
前面三个参数的含义和上文相同,第四个参数我们后面再讲,而这里的第五个参数就是initialBytesToStrip
,这里为4,表示获取完一个完整的数据包之后,忽略前面的四个字节,应用解码器拿到的就是不带长度域的数据包
3.基于偏移长度的拆包
下面这种方式二进制协议是更为普遍的,前面几个固定字节表示协议头,通常包含一些magicNumber,protocol version 之类的meta信息,紧跟着后面的是一个长度域,表示包体有多少字节的数据

只需要基于第一种情况,调整第二个参数既可以实现
new LengthFieldBasedFrameDecoder(Integer.MAX, 4, 4);
lengthFieldOffset
是4,表示跳过4个字节之后的才是长度域
4.基于可调整长度的拆包
有些时候,二进制协议可能会设计成如下方式

即长度域在前,header在后,这种情况又是如何来调整参数达到我们想要的拆包效果呢?
1.长度域在数据包最前面表示无偏移,lengthFieldOffset
为 0
2.长度域的长度为3,即lengthFieldLength
为3
2.长度域表示的包体的长度略过了header,这里有另外一个参数,叫做 lengthAdjustment
,包体长度调整的大小,长度域的数值表示的长度加上这个修正值表示的就是带header的包,这里是 12+2,header和包体一共占14个字节
最后,代码实现为
new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 3, 2, 0);
5.基于偏移可调整长度的截断拆包
更变态一点的二进制协议带有两个header,比如下面这种

拆完之后,HDR1
丢弃,长度域丢弃,只剩下第二个header和有效包体,这种协议中,一般HDR1
可以表示magicNumber,表示应用只接受以该magicNumber开头的二进制数据,rpc里面用的比较多
我们仍然可以通过设置netty的参数实现
1.长度域偏移为1,那么 lengthFieldOffset
为1
2.长度域长度为2,那么lengthFieldLength
为2
3.长度域表示的包体的长度略过了HDR2,但是拆包的时候HDR2也被netty当作是包体的的一部分来拆,HDR2的长度为1,那么 lengthAdjustment
为1
4.拆完之后,截掉了前面三个字节,那么 initialBytesToStrip
为 3
最后,代码实现为
new LengthFieldBasedFrameDecoder(Integer.MAX, 1, 2, 1, 3);
6.基于偏移可调整变异长度的截断拆包
前面的所有的长度域表示的都是不带header的包体的长度,如果让长度域表示的含义包含整个数据包的长度,比如如下这种情况

其中长度域字段的值为16, 其字段长度为2,HDR1的长度为1,HDR2的长度为1,包体的长度为12,1+1+2+12=16,又该如何设置参数呢?
这里除了长度域表示的含义和上一种情况不一样之外,其他都相同,因为netty并不了解业务情况,你需要告诉netty的是,长度域后面,再跟多少字节就可以形成一个完整的数据包,这里显然是13个字节,而长度域的值为16,因此减掉3才是真是的拆包所需要的长度,lengthAdjustment
为-3
这里的六种情况是netty源码里自带的六中典型的二进制协议,相信已经囊括了90%以上的场景,如果你的协议是基于长度的,那么可以考虑不用字节来实现,而是直接拿来用,或者继承他,做些简单的修改即可
【转】netty源码分析之LengthFieldBasedFrameDecoder的更多相关文章
- netty源码分析系列文章
netty源码分析系列文章 nettynetty源码阅读netty源码分析 想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...
- 【Netty源码分析】发送数据过程
前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...
- 【Netty源码分析】客户端connect服务端过程
上一篇博客[Netty源码分析]Netty服务端bind端口过程 我们介绍了服务端绑定端口的过程,这一篇博客我们介绍一下客户端连接服务端的过程. ChannelFuture future = boos ...
- netty源码分析之揭开reactor线程的面纱(二)
如果你对netty的reactor线程不了解,建议先看下上一篇文章netty源码分析之揭开reactor线程的面纱(一),这里再把reactor中的三个步骤的图贴一下 reactor线程 我们已经了解 ...
- netty源码分析之二:accept请求
我在前面说过了server的启动,差不多可以看到netty nio主要的东西包括了:nioEventLoop,nioMessageUnsafe,channelPipeline,channelHandl ...
- Netty源码分析(前言, 概述及目录)
Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...
- Netty源码分析第1章(Netty启动流程)---->第1节: 服务端初始化
Netty源码分析第一章: Server启动流程 概述: 本章主要讲解server启动的关键步骤, 读者只需要了解server启动的大概逻辑, 知道关键的步骤在哪个类执行即可, 并不需要了解每一步的 ...
- Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建
Netty源码分析第一章: Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...
- Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化
Netty源码分析第一章:Netty启动流程 第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...
随机推荐
- python函数 变量 递归
1 语法 #语法 def 函数名(参数1,参数2,参数3,...): '''注释''' 函数体 return 返回的值 #函数名要能反映其意义 返回值数=0:返回None放回值数=1:返回object ...
- ES6入门——类的概念
1.Class的基本用法 概述 JavaScript语言的传统方式是通过构造函数,定义并生成新对象.这种写法和传统的面向对象语言差异很大,下面是一个例子: function Point(x, y) { ...
- Ehcache.xml 配置及属性说明
1.配置样例 <?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStor ...
- 借助System.Linq.Dynamic, IQueryable根据排序字符串排序
在使用Entity Framework时,若有多个排序,需要OrderBy (OrderByDescending)再ThenBy (ThenByDescending) 假设需要根据Name升序排序,再 ...
- SQL语言DDL DML DCL TCL四种语言
1.DDL(Data Definition Language)数据库定义语言:DDL使我们有能力创建或删 除表格.可以定义索引(键),规定表之间的链接,以及施加表间的 约束. • 常见DDL 语句: ...
- Codewars, Leetcode, Hackerrank. Online Judges Reviews
http://jasonjl.me/blog/2015/03/30/practical-programming-practice-services/ Codewars, Leetcode, Hacke ...
- C# 算法题系列(一) 两数之和、无重复字符的最长子串
题目一 原题链接 https://leetcode-cn.com/problems/two-sum/ 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整 ...
- JqGrid中文文档之TreeGrid
几年之前写过一个非常简单的jqgrid属性说明. 今天又用到jqgrid这个控件了,捣鼓了许久,第一个treegrid完成了 jQuery("#list1").jqGrid({ u ...
- iis和apache共用80端口,IIS代理转发apache
为什么共用80端口应该不用多说了,服务器上程序运行环境有很多套,都想抢用80端口,所以就有了共用80端口的解决方案. 网上很多的教程一般都是设置APACHE使用默认80端口,代理转发IIS的网站,II ...
- galera mariadb集群恢复策略
1 galera mariadb首先MariaDB是一个数据库,可以看成是MySQL的一个分支,由于MySQL被SUN收购,所以MySQL面临着闭源的风险,当时MySQL之父Widenius并没有加入 ...