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

前置和处理平台(暂时)使用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. linux下的scp传输文件

    转载于:http://moyu2010.blog.163.com/blog/static/177439041201112710243064/,再次谢谢作者. 1.功能说明scp就是security c ...

  2. Dubbo直连方式

    目录 一.dubbo概述 1. 基本架构 2. dubbo 支持的协议 二.直连方法 三.创建服务提供者 1. 思路 1. 创建maven web 2. pom.xml 3. 创建实体 4. 创建服务 ...

  3. Netbox 开源 IPAM 管理工具搭建详细流程

    原文链接:Netbox 开源 IPAM 管理工具搭建详细流程 参考资料:https://netbox.readthedocs.io/en/stable/ PostgreSQL数据库安装 1.yum 下 ...

  4. Combining STDP and Reward-Modulated STDP in Deep Convolutional Spiking Neural Networks for Digit Recognition

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Abstract 灵长类视觉系统激发了深度人工神经网络的发展,使计算机视觉领域发生了革命性的变化.然而,这些网络的能量效率比它们的生物学对 ...

  5. css基本样式设置

    div中文字居中 如何让一个div中的文字水平和垂直居中?设置如下: 给定该div的长宽(或者二者只给出其一也可) .box{ height: 100px; width: 30%; text-alig ...

  6. springboot-swagger配置

    原文地址 https://www.cnblogs.com/softidea/p/6251249.html https://www.cnblogs.com/xiebq/p/9181517.html 1p ...

  7. 手写@Service、@Autowired、@Transactional注解,实现spring ioc和spring事务

    自定义@Service注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Custom ...

  8. WPF新手快速入门系列 3.MVVM

    [概要] 这一章主要讲述,讲述MVVM模式和用法. 如有学习过程中想交流学习.疑惑解答可以来此QQ群交流:580749909.(所有涉及到的源码都上传到了群文件里) 希望加群的人提问时尽量想清楚自己的 ...

  9. 关于对MyBatis.net框架的学习笔记( MyBatis.net是一款灵活性极大,sql由开发者自行在xml中编写, 轻量的ORM映射框架). 同时避免了sql硬编码到代码中不易维护的问题...

    对于为什么要用ORM,为什么又要选择MyBatis.net,这个问题希望读者自行查找资料.这里直接贴出相关的调试笔记. 步骤1)下载与引用. http://code.google.com/p/myba ...

  10. Redis秒杀实战-微信抢红包-秒杀库存,附案例源码(Jmeter压测)

    导读 前二天我写了一篇,Redis高级项目实战(点我直达),SpringBoot整合Redis附源码(点我直达),今天我们来做一下Redis秒杀系统的设计.当然啦,Redis基础知识还不过关的,先去加 ...