Netty 框架——TCP 粘包和拆包

Netty 框架——TCP 粘包和拆包

1. 产生的原因

在 TCP 协议中,发送端为了提高网络传输的效率,通常会使用优化算法,如 Nagle 算法,将多个小的数据包合并成一个较大的数据块一起发送。这是因为频繁的小数据包传输可能会导致效率低下,因此通过合并可以减少网络负载。但这种优化带来了一个问题:接收端无法明确分辨每个数据包的边界,因为 TCP 是面向流的协议,并没有明确的消息边界。这就导致了 粘包和拆包 问题。

  • 粘包:发送端连续发送多个数据包时,接收端接收到的可能是一个长数据包,无法区分各个消息的起始和结束位置。
  • 拆包:发送端发送一个较大的数据包时,接收端可能会将数据分割成多个小的包进行接收,导致无法还原出原始数据包。

2. 现象模拟

在正常编写程序时,粘包和拆包问题较为常见。通过以下模拟图可以观察到两种情况的不同表现:

image-20241207144022767

粘包和拆包现象
  • 粘包现象:当多个小数据包被合并为一个大数据包时,接收端难以判断每个小数据包的边界。

    image-20241207163914040

  • 拆包现象:当一个较大的数据包被拆成多个小数据包时,接收端需要处理拆分的不同数据块。

    image-20241207163928515

如上所示,两种情况的表现有所不同,通常需要通过协议设计来避免这些问题。

3. 解决方案

解决粘包和拆包问题的常见方法是使用 自定义协议编码器解码器。自定义协议能够帮助我们定义每个消息的结构,包括消息的长度,从而保证数据包的边界可以正确识别。

具体方案:
  • 使用自定义协议头部来存储消息的长度,接收端根据消息的长度来划分数据包的边界。
  • 使用 Netty 的编码器(Encoder)和解码器(Decoder)来处理消息的拆解与重组。

4. 核心源码实现

1. 自定义协议:MessageProtocal

在这个协议中,我们使用一个 len 字段来存储消息的长度,content 字段来存储消息的内容。

public class MessageProtocal {private int len;  // 消息的长度private byte[] content;  // 消息内容public int getLen() {return len;}public void setLen(int len) {this.len = len;}public byte[] getContent() {return content;}public void setContent(byte[] content) {this.content = content;}
}
2. 客户端发送数据:MyClientHandler

客户端通过 channelActive 方法向服务器一次性发送多条数据。在发送时,我们将每条消息的长度和内容分别存储到 MessageProtocal 对象中,并通过 ctx.writeAndFlush() 将消息发送给服务器。

public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocal> {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 一次发送十条数据for (int i = 0; i < 10; i++) {String msg = "今天天气还行" + i;byte[] content = msg.getBytes(Charset.forName("utf-8"));int length = msg.getBytes(Charset.forName("utf-8")).length;// 构建自定义协议MessageProtocal messageProtocal = new MessageProtocal();messageProtocal.setLen(length);messageProtocal.setContent(content);// 发送数据ctx.writeAndFlush(messageProtocal);}}
}
结果

image-20241207201221827

5. 其他解决办法

除了使用自定义协议来解决粘包和拆包问题之外,还有一些常见的解决办法,可以通过不同的方式优化和规避粘包和拆包问题。下面列出几种常见的解决方案:

5.1 固定长度的消息头

一种简单的解决方法是通过约定每个数据包的固定长度来规避粘包和拆包问题。具体来说,可以设置一个固定长度的消息头,消息头包含消息的长度。通过这种方式,接收端每次都能通过消息头来判断后续数据的边界。

方案说明:
  • 每个消息的开头添加固定长度的头部(通常是 4 字节表示消息的长度)。
  • 接收端可以通过读取消息头来确定消息的总长度,从而读取对应长度的消息数据。
示例代码:
// 固定长度协议头,假设协议头长度为4字节
public class FixedLengthProtocol {// 固定头长度为4字节,表示消息长度public static final int HEADER_LENGTH = 4;// 根据协议头长度确定消息体长度public static int getMessageLength(ByteBuf buffer) {return buffer.readInt();  // 读取4字节长度信息}public static void writeMessage(ByteBuf buffer, byte[] content) {// 写入消息头,包含消息的长度buffer.writeInt(content.length);// 写入消息体内容buffer.writeBytes(content);}
}
优点:
  • 简单易理解,能够快速实现。
  • 接收端根据消息头判断消息长度,能清晰地分辨每条消息的边界。
缺点:
  • 消息的大小必须是固定的,或者消息长度可能受到限制。
5.2 使用分隔符

另一种常见的解决方案是使用 分隔符 来区分每个数据包。这种方式是通过在每个消息的末尾添加特定的分隔符来标识消息的边界。例如,可以使用一些特殊字符(如换行符、标点符号等)作为消息的分隔符。

方案说明:
  • 发送的数据包末尾加上一个固定的分隔符(如 \n\0)。
  • 接收端接收到数据后,依据分隔符来切割数据。
示例代码:
public class DelimiterProtocol {// 消息分隔符private static final String DELIMITER = "\n";public static void writeMessage(ByteBuf buffer, String message) {buffer.writeBytes(message.getBytes());buffer.writeBytes(DELIMITER.getBytes());  // 添加分隔符}public static String readMessage(ByteBuf buffer) {int delimiterIndex = findDelimiterIndex(buffer);if (delimiterIndex != -1) {byte[] messageBytes = new byte[delimiterIndex];buffer.readBytes(messageBytes);buffer.readByte();  // 移除分隔符return new String(messageBytes, StandardCharsets.UTF_8);}return null;}// 查找分隔符的位置private static int findDelimiterIndex(ByteBuf buffer) {for (int i = 0; i < buffer.readableBytes(); i++) {if (buffer.getByte(i) == '\n') {return i;}}return -1;}
}
优点:
  • 实现简单且容易理解。
  • 可以支持任意大小的消息,不需要消息长度限制。
缺点:
  • 如果消息中有分隔符字符(例如 \n),就可能导致数据解析错误。
  • 必须确保每条消息都以分隔符结尾。
5.3 基于长度的协议

基于长度的协议类似于固定长度协议,但它采用动态长度的消息头来指示消息的长度。每条消息头部包含该消息的长度信息,接收端根据这个长度来读取消息内容。

方案说明:
  • 每个消息的开头包含一个表示消息长度的字段(通常是 4 字节整数)。
  • 接收端通过读取该长度字段来获取消息的长度,并根据该长度从缓冲区中提取消息数据。
示例代码:
public class LengthPrefixedProtocol {// 消息头长度4字节private static final int HEADER_LENGTH = 4;public static void writeMessage(ByteBuf buffer, byte[] content) {buffer.writeInt(content.length);  // 写入消息长度buffer.writeBytes(content);  // 写入消息内容}public static byte[] readMessage(ByteBuf buffer) {if (buffer.readableBytes() < HEADER_LENGTH) {return null;}int length = buffer.readInt();  // 读取消息长度byte[] content = new byte[length];buffer.readBytes(content);  // 读取消息内容return content;}
}
优点:
  • 支持变长消息,灵活性较高。
  • 消息解析清晰,容易通过消息长度来判断边界。
缺点:
  • 每个消息必须包含消息长度字段,相比其他协议,可能会带来更多的开销。
  • 对于较小的消息,可能会造成额外的内存和性能开销。
5.4 Netty 提供的解码器

Netty 本身也提供了现成的解码器来解决粘包和拆包问题。例如:

  • DelimiterBasedFrameDecoder:该解码器通过指定分隔符来进行数据包的拆解。
  • LengthFieldBasedFrameDecoder:该解码器通过指定一个长度字段来帮助解码器识别消息的边界。
示例代码:
// 使用 DelimiterBasedFrameDecoder 解决问题
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));  // 根据分隔符拆包
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new MyServerHandler());
优点:
  • Netty 提供的解码器封装了常见的拆包和粘包处理逻辑,使用起来非常方便。
  • 性能较好,适用于高性能的应用场景。
缺点:
  • 如果协议要求更为复杂,可能需要自己编写自定义的解码器。
  • 对于不常见的协议,Netty 内置的解码器可能无法满足需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/485931.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL靶场第九关攻略

我们的第九关需要用到时间盲注 使用条件&#xff1a;完全没有变化的页面 我们在了解一下时间盲注和布尔盲注的区别&#xff0c;时间盲注比布尔盲注多了一个if判断加上sleep()函数的运用 if(a,b,c) if判断句&#xff0c;a为条件&#xff0c;b、c为执行语句&#xff1b;如果a为…

STM32一keil5更换芯片后报错问题的解决。

目录 一、STM32型号认识二、报错问题三、常用的启动配置文件四、问题解决 一、STM32型号认识 二、报错问题 当我们在原来工程下修改芯片时&#xff0c;原本可以编译通过的代码突然很多报错。如下所示&#xff0c;这是因为我们的启动文件配置错误。对于不同型号的芯片其flash容量…

STM32 自学笔记

摘抄于大学期间记录在QQ空间的一篇自学笔记&#xff0c;当前清理空间&#xff0c;本来想直接删除掉的&#xff0c;但是感觉有些舍不得&#xff0c;因此先搬移过来。 RAM vs ROM vs FLASH 2013-09-05记录&#xff0c;ROM和RAM指的都是半导体存储器&#xff0c;ROM是Read Only …

深入解析 HTML Input 元素:构建交互性表单的核心

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

MBox20边缘计算网关:氢能车间数据采集的智慧引擎

氢能作为未来能源体系的重要组成部分&#xff0c;其安全、高效、环保的特性备受瞩目。在氢能车间的日常运营中&#xff0c;数据采集是确保生产流程优化、设备稳定运行及能效提升的关键环节。然而&#xff0c;面对氢能车间复杂多变的生产环境和海量数据&#xff0c;如何实现高效…

敏捷开发之路

1. 引言 最近有个企业软件开发项目&#xff0c;用户要求采用敏捷开发的方法实施项目。以前也参加过敏捷方法的培训&#xff0c;结合最近找的敏捷开发材料&#xff0c;形成了下面的敏捷实施过程内容。 以下采用了QAD量化敏捷开发方法&#xff0c;关于此方法详细参考内容见最后…

threejs相机辅助对象cameraHelper

为指定相机创建一个辅助对象&#xff0c;显示这个相机的视锥。 想要在场景里面显示相机的视锥&#xff0c;需要创建两个相机。 举个例子&#xff0c;场景中有个相机A&#xff0c;想要显示相机A的视锥&#xff0c;那么需要一个相机B&#xff0c;把B放在A的后面&#xff0c;两个…

Milvus向量数据库03-搜索理论

Milvus向量数据库03-搜索理论 1-ANN搜索 通过 k-最近邻&#xff08;kNN&#xff09;搜索可以找到一个查询向量的 k 个最近向量。kNN 算法将查询向量与向量空间中的每个向量进行比较&#xff0c;直到出现 k 个完全匹配的结果。尽管 kNN 搜索可以确保准确性&#xff0c;但十分耗…

解决git did not exit cleanly (exit code 128)问题

解决 git did not exit cleanly &#xff08;exit code 128&#xff09;问题 1、错误描述2、解决方法2.1 方法一2.2 方法二 1、错误描述 使用TortoiseGit进行操作时&#xff0c;总是提示下述错误。 2、解决方法 2.1 方法一 打开 TortoiseGit -> Settings 点击 Network&…

唇形同步视频生成工具:Wav2Lip

一、模型介绍 今天介绍一个唇形同步的工具-Wav2Lip&#xff1b;Wav2Lip是一种用于生成唇形同步&#xff08;lip-sync&#xff09;视频的深度学习算法&#xff0c;它能够根据输入的音频流自动为给定的人脸视频添加准确的口型动作。 &#xff08;Paper&#xff09; Wav2Lip模型…

ubuntu下Qt5自动编译配置QtMqtt环境(10)

文章目录 [toc]1、概述2、下载QtMqtt源码3、编译4、验证5、参考6、视频 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt网络编程 &#x1f448; 1、概述 Qt默认是不包含mqtt库的&#xff0c;如果需要使用到mqtt库就只能自己编译配置&#xff1b; 网络所有的…

verilog编程规范

verilog编程规范 文章目录 verilog编程规范前言一、代码划分二、verilog编码ABCDEFG 前言 高内聚&#xff0c;低耦合&#xff0c;干净清爽的代码 一、代码划分 高内聚&#xff1a; 一个功能一个模块干净的接口提取公共的代码 低耦合&#xff1a; 模块之间低耦合尽量用少量…

使用VScode 和 Keil搭建STM32的开发环境

目录 概述 1 Keil工具 1.1 Keil工具介绍 1.2 Keil 下载 1.3 安装Keil 1.4 Keil软件测试 2 VSCode软件 2.1 VSCode介绍 2.2 VSCode下载 2.3 安装VSCode 3 搭建STM32集成开发环境 3.1 安装Keil插件 3.2 参数配置 3.3 测试 4 配置头文件路径 4.1 参数配置 4.2 测…

【C++学习篇】map和set (set篇)

目录 1.map和set的使用 1.1序列式容器和关联式容器 2. set系列的使⽤ 2.1 set分类 2.2set类的介绍 2.3 set的构造和迭代器 2.4set的增删查 2.5 insert和迭代器遍历使⽤样例&#xff1a; 2.6 find和erase使⽤样例&#xff1a; 2.7multiset和set的差异 1.map和set的…

WGAN生成对抗网络数据生成

数据生成 | WGAN生成对抗网络数据生成 目录 数据生成 | WGAN生成对抗网络数据生成生成效果基本描述程序设计参考资料 生成效果 基本描述 1.WGAN生成对抗网络&#xff0c;数据生成&#xff0c;样本生成程序&#xff0c;MATLAB程序&#xff1b; 2.适用于MATLAB 2020版及以上版本&…

简易图书管理系统

javawebjspservlet 实体类 package com.ghx.entity;/*** author &#xff1a;guo* date &#xff1a;Created in 2024/12/6 10:13* description&#xff1a;* modified By&#xff1a;* version:*/ public class Book {private int id;private String name;private double pri…

c++笔记2

14、c的对象 对象和结构的区别&#xff1b; 结构&#xff1a;包含各种类型的变量&#xff1b; 对象&#xff1a;包含各种函数、和变量&#xff1b; 设计对象的时候引用class关键字创建类&#xff0c;和结构形状差不多&#xff1b; 将变量称之为属性&#xff0c;函数称之为方…

停止等待协议

理想传输条件有以下两个特点&#xff1a; 传输信道不产生差错不管发送发以多快的速度发送数据&#xff0c;接收方总是来得及处理收到的数据。 然而实际的网络都不具备以上两个理想条件&#xff0c;所以需要一个协议 “停止等待协议”就是每发送完一个分组就停止发送&#xf…

python | print() 函数常被忽略的几点用法

在 python 编程中&#xff0c;print() 是最为基础和常用的函数。 也正因如此&#xff0c;print() 函数的一些基础用法常常被我们初学者所忽略&#xff0c;典型的有&#xff1a;换行问题、间隔符使用及格式化输出等。 一、print() 换行问题 1、默认情况下&#xff0c;每一个 …

《鸣潮》运行时电脑提示“d3dx9_41.dll丢失”是什么原因?“缺失d3dx9_41.dll文件”的解决方法和预防方案

游戏运行时文件丢失与报错解决方案&#xff1a;《鸣潮》提示“d3dx9_41.dll丢失”怎么办&#xff1f; 大家好&#xff0c;我是一名在软件开发领域有着丰富经验的从业者。在游戏爱好者的世界里&#xff0c;遇到游戏运行时提示文件丢失或损坏的情况并不少见。今天&#xff0c;我…