高性能网络框架Netty的TCP拆包、粘包解决方案

简介: 简单地说,网络通信时由于TCP会对传输的数据报进行对用户透明的拆分与重新组装,然后将拆分后的分别发送,而我们接收时要获取发送时的数据报,如何再对其拆分与组装,以便于我们能知道报文的意思,这个提取报文的过程就是TCP的拆包与粘包,在我们自己做底层的通信设计时,这是必须要考虑的。

简单地说,网络通信时由于TCP会对传输的数据进行对用户透明的拆分与重新组装,然后将拆分后的分别发送,而我们接收时要获取发送时的数据报,如何再对其拆分与组装,以便于我们能知道报文的意思,这个提取报文的过程就是TCP的拆包与粘包,在我们自己做底层的通信设计时,这是必须要考虑的。结合最近在做一个和通信相关的项目,本文讲几个经典且常用的几种粘包与拆包方法及其在Netty中的实现,Netty是高性能的通信框架,Netty和另一个通信框架Apache的MINA比较像,而且他们作者相同。关于Netty4与MINA2我做过一次比较总结,并将PPT上传在了网上,地址:http://share.csdn.net/slides/8056

进入主题,Netty提供的拆包与粘包工具类:

1、 基于长度字段
 io.netty.handler.codec.LengthFieldPrepender

类关系图如下:

原理和下面的io.netty.handler.codec.LengthFieldBasedFrameDecoder原理类似,不同是这个在编码的过程使用,

例如原报文数据如下:

 +----------------------------+
  | "HELLO, WORLD" |
 +----------------------------+

长度占2个字节且不包含本身的拆包粘包结果如下:

 +-----------+--------------------------+
  | 0x000C | "HELLO, WORLD" |
 +-----------+--------------------------+

长度占2个字节且包含本身的拆包粘包结果如下:

 +------------+----------------------------+
  | 0x000E | "HELLO, WORLD" |
 +------------+----------------------------+

 2、基于界定符解码器
 io.netty.handler.codec.DelimiterBasedFrameDecoder

类关系图如下:

原理如下:

假设收到的报文如下:

 +--------------------+
  | ABC\nDEF\r\n |
 +--------------------+

如果以‘\n’为界定符,则拆包粘包后的报文就是:

 +--------+-------+
  | ABC | DEF |
 +--------+-------+

如果以‘\r\n’为界定符,则拆包粘包后的报文就是:

 +-----------------+
  | ABC\nDEF |
 +-----------------+


 3、基于定长解码器
 io.netty.handler.codec.FixedLengthFrameDecoder

类关系图如下:


 定长就是指定了报文的长度,解析时就是按长度组合截取,原理如下:

假设接收到的报文如下:

 +----+-----+---------+----+
  | A | BC | DEFG | HI |
 +----+-----+---------+----+

当定长参数为3时,拆包与粘包的结果是:

 +--------+-------+------+
  | ABC | DEF | GHI |
 +--------+-------+------+


4、基于长度字段解码器
 io.netty.handler.codec.LengthFieldBasedFrameDecoder

类关系图如下:


 所谓长字段就是在报文里有说明报文总长度的字段,其实在TCP的报文规则里就用的这个方法,在头部存放报文总长或除报头的内容总长,具体如下:

长度包含长度字段本身且不排除本身的拆包与粘包:

 lengthFieldOffset   = 0     长度字段偏移量
 lengthFieldLength   = 2    长度字段所占长度
 lengthAdjustment    = 0   
 initialBytesToStrip = 0      (要排除的用于初始化的偏移位置)

         解码前 (14 bytes)                                    解码后 (14 bytes)
 +------------+---------------------------+             +------------+---------------------------+
  | Length  |    Actual Content   |   ----->    | Length  |    Actual Content   |
  | 0x000C | "HELLO, WORLD" |             | 0x000C | "HELLO, WORLD" |
 +------------+----------------------------+            +-----------+----------------------------+

长度包含长度字段本身且排除本身的拆包与粘包:

 lengthFieldOffset   = 0     长度字段偏移量
 lengthFieldLength   = 2     长度字段所占长度
 lengthAdjustment    = 0
 initialBytesToStrip = 2 (排除头部)

            解码前 (14 bytes)                        解码后 (12 bytes)
 +------------+----------------------------+           +---------------------------+
  | Length  |    Actual Content    |     ----->|   Actual Content    |
  | 0x000C | "HELLO, WORLD" |            | "HELLO, WORLD" |
 +------------+----------------------------+          +----------------------------+
 

长度包含长度字段本身且不排除本身的拆包与粘包:

 lengthFieldOffset   =  0   长度字段偏移量
 lengthFieldLength   =  2   长度字段偏移量
 lengthAdjustment    = -2  调整长度 (长度字段所占长度)
 initialBytesToStrip =  0

      解码前 (14 bytes)                                   解码后 (14 bytes)
 +------------+----------------------------+             +-----------+----------------------------+
  | Length  |    Actual Content    |   ----->    | Length  |    Actual Content   |
  | 0x000E | "HELLO, WORLD" |              | 0x000E | "HELLO, WORLD" |
 +------------+----------------------------+             +-----------+----------------------------+

 

有外部头部的拆包与粘包:

 lengthFieldOffset   = 2        长度字段偏移量 ( = 外部头部Header 1的长度)
 lengthFieldLength   = 3      长度字段占用字节数
 lengthAdjustment    = 0
 initialBytesToStrip = 0

                解码前 (17 bytes)                                                                  解码后 (17 bytes)
 +--------------+--------------+--------------------------+              +-------------+---------------+--------------------------+
  | Header 1 |     Length   |     Actual Content    |    ----->   | Header 1 |    Length    |     Actual Content    |
  |  0xCAFE   | 0x00000C | "HELLO, WORLD" |                |  0xCAFE   | 0x00000C | "HELLO, WORLD" |
 +--------------+--------------+--------------------------+              +--------------+--------------+--------------------------+

长度字段在前且有扩展头部的拆包与粘包:

 lengthFieldOffset   = 0    长度字段偏移量
 lengthFieldLength   = 3   长度字段占用字节数
 lengthAdjustment    = 2 ( Header 1 的长度)
 initialBytesToStrip = 0

                        解码前 (17 bytes)                                                   解码后 (17 bytes)
 +----------------+---------------+---------------------------+              +---------------+----------------+---------------------------+
  |    Length   |  Header 1 |    Actual Content   |    ----->    |    Length   |   Header 1 |   Actual Content    |
  | 0x00000C |  0xCAFE  | "HELLO, WORLD" |               | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
 +----------------+---------------+---------------------------+              +---------------+----------------+---------------------------+

多扩展头部的拆包与粘包:

 lengthFieldOffset   = 1    长度字段偏移量(=头HDR1的长度)
 lengthFieldLength   = 2   长度字段占用字节数
 lengthAdjustment    = 1  调整长度(= HDR2的长度)
 initialBytesToStrip = 3     排除的偏移量(= the length of HDR1 + LEN)

                       解码前 (16 bytes)                                           解码后 (13 bytes)
 +----------+-----------+----------+----------------------------+              +----------+---------------------------+
  | HDR1 | Length  | HDR2 |   Actual Content     |     ----->    | HDR2 |    Actual Content   |
  | 0xCA | 0x000C |  0xFE | "HELLO, WORLD" |                |  0xFE | "HELLO, WORLD" |
 +---------+------------+----------+---------------------------+               +----------+---------------------------+

调整的多扩展头部的拆包与粘包:

 lengthFieldOffset   =  1        长度字段偏移量(=头HDR1的长度)
 lengthFieldLength   =  2      长度字段占用字节数
 lengthAdjustment    = -3      (= the length of HDR1 + LEN, negative)
 initialBytesToStrip =  3        排除的偏移量(= the length of HDR1 + LEN)

                   解码前 (16 bytes)                                                        解码后 (13 bytes)
 +---------+-----------+---------+--------------------------+                 +---------+-------------------------+
  | HDR1 | Length  | HDR2 |    Actual Content     |     ----->     | HDR2 |     Actual Content   |
  |  0xCA  | 0x0010 |  0xFE   | "HELLO, WORLD" |                   |  0xFE  | "HELLO, WORLD" |
 +---------+-----------+---------+--------------------------+                 +---------+-------------------------+


5、基于换行符解码器
 io.netty.handler.codec.LineBasedFrameDecoder

类关系图如下:

英文的解释是:A decoder that splits the received ByteBufs on line endings.

一行的结束标志包括: "\n" 和 "\r\n",所以又属于io.netty.handler.codec.FixedLengthFrameDecoder的范畴。


6、关于Netty中的ByteBuf

由于Netty底层是ByteBuf的结构特殊,具有双指针(读指针和写指针)如下:

所以相比MINA的ChannelBuffer的性能要高很多,这也是拆包与粘包的应用之处,就是如何将byte数组转换成我们想要的Messgae。

从类的关系图中我们可以看到Netty里两种数据流向,其实这是ChannelPipeline(管道)中的两种处理链,如图所示:

所以处理连接是继承类ChannelInboundHandlerAdapter,如下:


用Netty创建服务并且用能到这些拆包与粘的地方的代码如下(第36行处):


总结一下:和拆包与粘包相关的还有就是大小端,也就是高位与低位的位置问题,这些都与编解码相关,通信相关的问题也是这些问题,

今天基本上讲清楚了TCP拆包与粘包,其中引例来自Netty中的注释,我翻译了一下,可能有些地方翻译的不是很到位,感兴趣的可以直接看Netty4的源码。

目录
相关文章
|
9月前
|
小程序 前端开发
2025商业版拓展校园圈子论坛网络的创新解决方案:校园跑腿小程序系统架构
校园跑腿小程序系统是一款创新解决方案,旨在满足校园配送需求并拓展校友网络。跑腿员可接单配送,用户能实时跟踪订单并评价服务。系统包含用户、客服、物流、跑腿员及订单模块,功能完善。此外,小程序增设信息咨询发布、校园社区建设和活动组织等功能,助力校友互动、经验分享及感情联络,构建紧密的校友网络。
378 1
2025商业版拓展校园圈子论坛网络的创新解决方案:校园跑腿小程序系统架构
|
4月前
|
缓存 人工智能 API
API接口调用中的网络异常及解决方案
淘宝API是淘宝开放平台提供的接口集合,支持商品、交易、用户、营销等数据交互。开发者需注册获取App Key,通过签名认证调用API,结合沙箱测试、OAuth授权与安全策略,实现订单管理、数据监控等应用,提升电商自动化与数据分析能力。
|
5月前
|
监控 前端开发 安全
Netty 高性能网络编程框架技术详解与实践指南
本文档全面介绍 Netty 高性能网络编程框架的核心概念、架构设计和实践应用。作为 Java 领域最优秀的 NIO 框架之一,Netty 提供了异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。本文将深入探讨其 Reactor 模型、ChannelPipeline、编解码器、内存管理等核心机制,帮助开发者构建高性能的网络应用系统。
407 0
|
8月前
|
Docker 容器
Docker网关冲突导致容器启动网络异常解决方案
当执行`docker-compose up`命令时,服务器网络可能因Docker创建新网桥导致IP段冲突而中断。原因是Docker默认的docker0网卡(172.17.0.1/16)与宿主机网络地址段重叠,引发路由异常。解决方法为修改docker0地址段,通过配置`/etc/docker/daemon.json`调整为非冲突段(如192.168.200.1/24),并重启服务。同时,在`docker-compose.yml`中指定网络模式为`bridge`,最后通过检查docker0地址、网络接口列表及测试容器启动验证修复效果。
1401 39
|
10月前
|
小程序 UED
拓展校友网络的创新解决方案:校园论坛圈子小程序+跑腿+二手市场系统
这是一款基于小程序的校园跑腿服务平台,支持多种注册登录方式、下单支付、跑腿接单配送、订单跟踪评价及物流查询功能,并配备客服模块提升用户体验。系统包含用户、客服、物流、跑腿员和订单五大核心模块,功能完善。此外,平台还拓展了校友网络功能,如信息咨询发布、校园社区建设和活动组织等,旨在增强校友互动与联系,形成紧密的校友生态。
342 4
|
11月前
|
安全 网络安全 虚拟化
Hyper-V网络连接无响应解决方案
当Hyper-V虚拟机出现网络连接无响应时,可从以下方面排查:1) 检查物理网络连接,确保设备正常;2) 验证虚拟网络配置,包括虚拟交换机和网络适配器设置;3) 更新驱动程序以解决兼容性问题;4) 调整防火墙和安全软件设置;5) 重启相关服务和设备;6) 使用命令行工具诊断网络问题;7) 检查BIOS中虚拟化技术是否启用;8) 排查IP冲突和其他日志错误。综合以上步骤,可有效修复网络连接故障。
|
监控 BI 网络安全
为何教育机构需要强大的网络安全解决方案
近年来,教育行业从传统课堂快速转向在线课程和虚拟教室,疫情加速了这一进程。然而,数字化转型也带来了网络安全风险。身份治理与管理(IGA)解决方案如ManageEngine的ADManager Plus,能有效保护教育机构免受网络攻击,确保数据安全、简化用户管理并实现合规性。通过自动化流程,它不仅提升了安全性,还减轻了IT管理员的工作负担,确保资源访问的无缝性和准确性。
259 11
|
机器学习/深度学习 人工智能 运维
简化多云网络复杂度,谈谈F5多云解决方案的破局之道
简化多云网络复杂度,谈谈F5多云解决方案的破局之道
188 7
|
监控 安全 网络安全
云计算与网络安全:技术挑战与解决方案
随着云计算技术的飞速发展,其在各行各业的应用越来越广泛。然而,随之而来的网络安全问题也日益凸显。本文将从云服务、网络安全和信息安全等技术领域出发,探讨云计算面临的安全挑战及相应的解决方案。通过实例分析和代码示例,旨在帮助读者更好地理解云计算与网络安全的关系,提高网络安全防护意识。