高并发压测时,发现来自网关的消息出现粘包现象;分包就是势在必行的

前置和处理平台(暂时)使用netty通话,由于都是服务器平台使用DelimiterBasedFrameDecoder来解决分包

和网关的通信,找出包长的字段,使用LengthFieldBasedFrameDecoder来解决分包;

这个类拥有很多构造器,对于底层的通信协议,只要上报的数据有字段标识了变长内容的长度,可以通过计算得到包长的,都可以通过该类解决;

剩下的两种文中也有详细说明

LineBasedFrameDecoder解码器

LineBasedFrameDecoder是回车换行解码器,如果用户发送的消息以回车换行符作为消息结束的标识,则可以直接使用Netty的LineBasedFrameDecoder对消息进行解码,只需要在初始化Netty服务端或者客户端时将LineBasedFrameDecoder正确的添加到ChannelPipeline中即可,不需要自己重新实现一套换行解码器。

FixedLengthFrameDecoder解码器

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

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

下文对netty的粘包问题有比较详细的说明

https://blog.csdn.net/a925907195/article/details/74942472

Netty是目前业界最流行的NIO框架之一,它的健壮性、高性能、可定制和可扩展性在同类框架中都是首屈一指。它已经得到了成百上千的商业项目的验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他的业界主流RPC框架,例如:Dubbo、Google 开源的gRPC、新浪微博开源的Motan、Twitter 开源的finagle也使用Netty来构建高性能的异步通信能力。另外,阿里巴巴开源的消息中间件RocketMQ也使用Netty作为底层通信框架。

TCP黏包/拆包

TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

粘包问题的解决策略

由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决。业界的主流协议的解决方案,可以归纳如下: 
1. 消息定长,报文大小固定长度,例如每个报文的长度固定为200字节,如果不够空位补空格; 
2. 包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分; 
3. 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段; 
4. 更复杂的自定义应用层协议。

Netty粘包和拆包解决方案

Netty提供了多个解码器,可以进行分包的操作,分别是: 
* LineBasedFrameDecoder 
* DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包) 
* FixedLengthFrameDecoder(使用定长的报文来分包) 
* LengthFieldBasedFrameDecoder

第四种分包的参数比较复杂

转自https://blog.csdn.net/thinking_fioa/article/details/80573483

LengthFieldBasedFrameDecoder - 参数说明
@author 鲁伟林
网上诸多博客对于LengthFieldBasedFrameDecode解码器的使用,翻译和解释过于死板,难于理解,特别是其构造函数的6个参数的解释,过于字面化解释。
该博客尽量保证通俗易懂,帮组读者理解和使用。读者可以选择读英文文档。
工作量:
1. 详细讲解LengthFieldBasedFrameDecode中6个参数的作用和使用。maxFrameLength, lengthFieldOffset, lengthFieldLength,
lengthAdjustment, initialBytesToStrip, failFast。
2. 给出多个实例,帮助理解和使用LengthFieldBasedFrameDecode解码器。

GitHub项目地址:
https://github.com/thinkingfioa/netty-learning/tree/master/netty-private-protocol
博客地址:
https://blog.csdn.net/thinking_fioa
Netty专栏地址:
https://blog.csdn.net/column/details/22861.html
1. LengthFieldBasedFrameDecoder作用
LengthFieldBasedFrameDecoder解码器自定义长度解决TCP粘包黏包问题。所以LengthFieldBasedFrameDecoder又称为: 自定义长度解码器

1.1 TCP粘包和黏包现象
  1. TCP粘包是指发送方发送的若干个数据包到接收方时粘成一个包。从接收缓冲区来看,后一个包数据的头紧接着前一个数据的尾。

2. 当TCP连接建立后,Client发送多个报文给Server,TCP协议保证数据可靠性,但无法保证Client发了n个包,服务端也按照n个包接收。Client端发送n个数据包,Server端可能收到n-1或n+1个包。

1.2 为什么出现粘包现象
  1. 发送方原因: TCP默认会使用Nagle算法。而Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。所以,正是Nagle算法造成了发送方有可能造成粘包现象。

2. 接收方原因: TCP接收方采用缓存方式读取数据包,一次性读取多个缓存中的数据包。自然出现前一个数据包的尾和后一个收据包的头粘到一起。

1.3 如何解决粘包现象
1. 添加特殊符号,接收方通过这个特殊符号将接收到的数据包拆分开 - DelimiterBasedFrameDecoder特殊分隔符解码器

2. 每次发送固定长度的数据包 - FixedLengthFrameDecoder定长编码器

3. 在消息头中定义长度字段,来标识消息的总长度 - LengthFieldBasedFrameDecoder自定义长度解码器

2. LengthFieldBasedFrameDecoder怎么使用
1. LengthFieldBasedFrameDecoder本质上是ChannelHandler,一个处理入站事件的ChannelHandler

2. LengthFieldBasedFrameDecoder需要加入ChannelPipeline中,且位于链的头部
---------------------
作者:thinking_fioa
来源:CSDN
原文:https://blog.csdn.net/thinking_fioa/article/details/80573483
版权声明:本文为博主原创文章,转载请附上博文链接!

3. LengthFieldBasedFrameDecoder - 6个参数解释
LengthFieldBasedFrameDecoder是自定义长度解码器,所以构造函数中6个参数,基本都围绕那个定义长度域,进行的描述。

1. maxFrameLength - 发送的数据帧最大长度

2. lengthFieldOffset - 定义长度域位于发送的字节数组中的下标。换句话说:发送的字节数组中下标为${lengthFieldOffset}的地方是长度域的开始地方

3. lengthFieldLength - 用于描述定义的长度域的长度。换句话说:发送字节数组bytes时, 字节数组bytes[lengthFieldOffset, lengthFieldOffset+lengthFieldLength]域对应于的定义长度域部分

4. lengthAdjustment - 满足公式: 发送的字节数组bytes.length - lengthFieldLength = bytes[lengthFieldOffset, lengthFieldOffset+lengthFieldLength] + lengthFieldOffset + lengthAdjustment

5. initialBytesToStrip - 接收到的发送数据包,去除前initialBytesToStrip位

6. failFast - true: 读取到长度域超过maxFrameLength,就抛出一个 TooLongFrameException。false: 只有真正读取完长度域的值表示的字节之后,才会抛出 TooLongFrameException,默认情况下设置为true,建议不要修改,否则可能会造成内存溢出

7. ByteOrder - 数据存储采用大端模式或小端模式

代码:

public LengthFieldBasedFrameDecoder(
ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset,
int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip,
boolean failFast) {
//...
}
划重点: 参照一个公式写,肯定没问题:
公式: 发送数据包长度 = 长度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment

4. 举例解释参数如何写
客户端多次发送"HELLO, WORLD"字符串给服务端。"HELLO, WORLD"共12字节(12B)。长度域中的内容是16进制的值,如下:

1. 0x000c -----> 12

2. 0x000e -----> 14

4.1 场景1
数据包大小: 14B = 长度域2B + "HELLO, WORLD"

解释:

如上图,长度域的值为12B(0x000c)。希望解码后保持一样,根据上面的公式,参数应该为:

1. lengthFieldOffset = 0

2. lengthFieldLength = 2

3. lengthAdjustment = 0 = 数据包长度(14) - lengthFieldOffset - lengthFieldLength - 长度域的值(12)

4. initialBytesToStrip = 0 - 解码过程中,没有丢弃任何数据

4.2 场景2
数据包大小: 14B = 长度域2B + "HELLO, WORLD"

解释:

上图中,解码后,希望丢弃长度域2B字段,所以,只要initialBytesToStrip = 2即可。其他与场景1相同

1. lengthFieldOffset = 0

2. lengthFieldLength = 2

3. lengthAdjustment = 0 = 数据包长度(14) - lengthFieldOffset - lengthFieldLength - 长度域的值(12)

4. initialBytesToStrip = 2 解码过程中,没有丢弃2个字节的数据

4.3 场景3
数据包大小: 14B = 长度域2B + "HELLO, WORLD"。与场景1不同的是:场景3中长度域的值为14(0x000E)

解释:

如上图,长度域的值为14(0x000E)。希望解码后保持一样,根据上面的公式,参数应该为:

1. lengthFieldOffset = 0

2. lengthFieldLength = 2

3. lengthAdjustment = -2 = 数据包长度(14) - lengthFieldOffset - lengthFieldLength - 长度域的值(14)

4. initialBytesToStrip = 0 - 解码过程中,没有丢弃任何数据

4.4 场景4
场景4在长度域前添加2个字节的Header。长度域的值(0x00000C) = 12。总数据包长度: 17=Header(2B) + 长度域(3B) + "HELLO, WORLD"

解释

如上图。编码解码后,长度保持一致,所以initialBytesToStrip = 0。参数应该为:

1. lengthFieldOffset = 2

2. lengthFieldLength = 3

3. lengthAdjustment = 0 = 数据包长度(17) - lengthFieldOffset(2) - lengthFieldLength(3) - 长度域的值(12)

4. initialBytesToStrip = 0 - 解码过程中,没有丢弃任何数据

4.5 场景5
与场景4不同的地方是: Header与长度域的位置换了。总数据包长度: 17=长度域(3B) + Header(2B) + "HELLO, WORLD"

解释

如上图。编码解码后,长度保持一致,所以initialBytesToStrip = 0。参数应该为:

1. lengthFieldOffset = 0

2. lengthFieldLength = 3

3. lengthAdjustment = 2 = 数据包长度(17) - lengthFieldOffset(0) - lengthFieldLength(3) - 长度域的值(12)

4. initialBytesToStrip = 0 - 解码过程中,没有丢弃任何数据

4.6 场景6 - 终极复杂案例
如下图,"HELLO, WORLD"域前有多个字段。总数据长度: 16 = HEADER1(1) + 长度域(2) + HEADER2(1) + "HELLO, WORLD"

1. lengthFieldOffset = 1

2. lengthFieldLength = 2

3. lengthAdjustment = 1 = 数据包长度(16) - lengthFieldOffset(1) - lengthFieldLength(2) - 长度域的值(12)

4. initialBytesToStrip = 0 - 解码过程中,没有丢弃任何数据

4.7 实际案例
我在项目Netty-private-protocol开发实际使用了LengthFieldBasedFrameDecoder自定义长度编码器。在消息Header的前面添加4个字节(int型),用于表示整个数据包的长度,欢迎一起交流。所以,我的参数是:

1. lengthFieldOffset = 0

2. lengthFieldLength = 4

3. lengthAdjustment = -4 = 数据包长度(msgLen) - lengthFieldOffset(0) - lengthFieldLength(4) - msgLen

4. initialBytesToStrip = 0

5. 总结
请记住公式: 发送数据包长度 = 长度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment。
---------------------
作者:thinking_fioa
来源:CSDN
原文:https://blog.csdn.net/thinking_fioa/article/details/80573483
版权声明:本文为博主原创文章,转载请附上博文链接!

netty字节分包的更多相关文章

  1. tar 压缩和解压缩使用笔记

    tar 压缩和解压缩使用笔记 1 文件 1.1 打包 1.1 压缩 $ tar czf myfile.txt.tar.gz ./myfile.txt 1.2 解压缩 解压缩到目录: $ mkdir o ...

  2. 示例:Netty 处理 TCP数据分包协议

    一个.Netty解决TCP协议的数据分包的想法 我们知道通过TCP协议发送接收数据时,假设数据过大.接收到的数据会是分包的.比方:                                   ...

  3. Netty之粘包分包

    粘包现象 客户端在一个for循环内连续发送1000个hello给Netty服务器端, Socket socket = new Socket("127.0.0.1", 10101); ...

  4. netty之粘包分包的处理

    1.netty在进行字节数组传输的时候,会出现粘包和分包的情况.当个数据还好,如果数据量很大.并且不间断的发送给服务器,这个时候就会出现粘包和分包的情况. 2.简单来说:channelBuffer在接 ...

  5. 根据首尾字节的tcp分包断包算法

    这个算是我的一点小总结吧,放出来分享给大家,原来在网上找这种算法都找了N久没找到,自己写也是走了许多弯路,就放出来遛一遛吧 大家将就这个看看, 这是其中的一个主要的方法,其余的我就不放出来了,其中的I ...

  6. Netty入门(四)ByteBuf 字节级别的操作

     Netty 中使用 ByteBuf 代替 Java NIO 提供的 ByteBuffer 作为字节的容器. 一.索引 ByteBuf 提供两个指针变量支持读和写操作,读操作使用 readerInde ...

  7. 基于Netty的RPC架构学习笔记(十一):粘包、分包分析,如何避免socket攻击

    文章目录 问题 消息如何在管道中流转 源码解析 AbstractNioSelector.java AbstractNioWorker.java NioWorker.java DefaultChanne ...

  8. netty字符串流分包

    @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Obj ...

  9. python分包写入文件,写入固定字节内容,当包达到指定大小时继续写入新文件

    第6行通过 for 循环控制生成 .log 文件的数量 第8行,如果该文件存在时先进行清空,然后再进行写入操作 第13行,将文件大小的单位转为MB 第14行,如果文件大小超过1MB时,跳出当前循环,重 ...

随机推荐

  1. 转圈游戏C++

    转圈游戏 问题描述 n 个小伙伴(编号从 0 到 n-1)围坐一圈玩游戏.按照顺时针方向给 n 个位置编号,从0 到 n-1.最初,第 0 号小伙伴在第 0 号位置,第 1 号小伙伴在第 1 号位置, ...

  2. 数字货币比特币以太坊买卖五档行情数据API接口

    数字货币比特币以太坊买卖五档行情数据API接口       数字货币一般包含比特币BTC.以太坊ETH.瑞波币XRP.泰达币USDT.比特币现金BCH.比特币SV.莱特币LTC.柚子币EOS.OKB. ...

  3. 第六篇 Scrum冲刺博客

    一.会议图片 二.项目进展 成员 已完成情况 今日任务 冯荣新 购物车列表,购物车工具栏 博客撰写 陈泽佳 静态结构 自定义图片组件,提交功能 徐伟浩 协助前端获取数据 协助前端获取数据 谢佳余 未完 ...

  4. java23种设计模式——三、工厂模式

    源码在我的github和gitee中获取 工厂模式 工厂模式介绍 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式.著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在J ...

  5. JAVA虚拟机故障诊断总结

    一.JAVA运行时数据区               1.堆(-Xmx与-Xms):所有线程共享.  目的:用来存放对象实例.所有对象实例和数组都要在堆上分配内存.JAVA堆是垃圾收集器管理的主要区域 ...

  6. [FJOI2020]染色图的联通性问题 题解

    FJOI2020 D1T2 题目大意 给出一个由 $n$ 个点 $m$ 条边构成的染色无向图,求删去每一个点及与其相连的边后图中不连通的同色点对数量.$n,m\leq 10^5$. 思路分析 可以想到 ...

  7. caoz:数据分析这点事

    http://www.wocaoseo.com/thread-53-1-1.html 先声明一下,按照传统的定义,我还真不是数据分析高手,各种关联算法,只会最简单的一种(话说不少场合还算管用):各种挖 ...

  8. Laravel chunk和chunkById的坑

    Laravel chunk和chunkById的坑 公司中的项目在逐渐的向Laravel框架进行迁移.在编写定时任务脚本的时候,用到了chunk和chunkById的API,记录一下踩到的坑. 一.前 ...

  9. 从后端到前端之Vue(六)表单组件

    表单组件 做项目的时候会遇到一个比较头疼的问题,一个大表单里面有好多控件,一个一个做设置太麻烦,更头疼的是,需求还总在变化,一会多选.一会单选.一会下拉的,变来变去的烦死宝宝了. 那么怎么解决这个问题 ...

  10. Kubernetes-14:一文详解Pod、Node调度规则(亲和性、污点、容忍、固定节点)

    Kubernetes Pod调度说明 简介 Scheduler 是 Kubernetes 的调度器,主要任务是把定义的Pod分配到集群的节点上,听起来非常简单,但要考虑需要方面的问题: 公平:如何保证 ...