当前位置: 首页 > news >正文

网络开发基础(游戏)之 粘包分包

 粘(nián)包、分包

在网络通信中,TCP协议是面向流的协议,没有消息边界概念,粘包和分包是常见的问题。在某种情况下(例如网络环境不稳定)就会导致"粘包"和"分包"问题:

  • 粘包:发送方发送的多个数据包被接收方当作一个数据包接收

  • 分包:发送方发送的一个数据包被接收方拆分成多个数据包接收

分包和粘包可能同时出现。

        一般有三种方法可以解决粘包和分包问题,分别是长度信息法固定长度法结束符号法。一般的游戏开发会在每个数据包前面加上长度字符,以方便解析,即一般会使用长度信息法来处理。

长度信息法

思路

在每个数据包前面加上长度信息。每次接收到数据后,先读取表示长度的字节,如果缓冲区的数据长度大于或等于要取的字节数,则取出相应的字节;否则等待下一次数据接收。

长度数据的大小

游戏程序一般会使用16位整数(ushort)或32位整数(uint)来存放长度信息,16位整数的取值范围是0~65535,32位整数的取值范围是0~4294967295。对于大部分游戏,网络消息的长度很难超过65535字节,使用16位整数来存放长度信息较合适。

Array.Copy数组复制

将一个数组的一部分元素复制到另一个数组中。

参数

说明

sourceArray

源数组(从这个数组中复制数据)

sourceIndex

从源数组中的第几个索引开始复制

destinationArray

目标数组(将数据复制到该数组)

destinationIndex

从目标数组的第几个索引开始存复制的数据

length

被复制的数据的长度

var array1 = new int[] { 1, 2, 3, 4, 5, 6 };
var array2 = new int[8];
Array.Copy(array1, 1, array2, 2, array1.Length - 1);

逻辑代码

实现该逻辑的方法有很多,下面给一个参考的写法

核心思想就是定义一个缓冲区(readBuff)和一个指示缓冲区有效长度的变量(lastRemainCount:主要用来记录缓冲区中未处理的有效数据长度,上次遗留的数据长度会被记录)。

处理粘包:读取长度信息,判断有效数据长度如果大于长度信息,说明有粘包现象,但是可以从数据中取得一个完整的消息。剩余的消息继续通过While循环来进行截取,直到无法获得一条完整的消息。

处理分包:读取长度信息,判断有效数据长度如果小于长度信息,说明有分包现象,无法从数据中取得一个完整的消息。将该数据缓存到缓冲区readBuff中,并且更新缓冲区有效长度lastRemainCount,跳出While循环。等到下一次接收到数据时,就将缓冲区的数据和新接收的数据进行拼接,然后继续上述处理,直到无法获得一条完整的消息。

//数据缓存区
private static byte[] readBuff = new byte[1024];
//头部信息长度
private static int headSize = sizeof(UInt16);
//上次未处理完的数据
private static int lastRemainCount = 0;   /// <summary>
/// 处理分包粘包
/// </summary>
/// <param name="dataBytes">接收的数据</param>
/// <param name="dataIndex">数据的有效长度</param>
public static void DecodeMsg(byte[] dataBytes, int dataIndex)
{Array.Copy(dataBytes, 0, readBuff, lastRemainCount, dataIndex);var sumCount = lastRemainCount + dataIndex;//未处理的总数据长度var buffIndex = 0;while (true){if(sumCount <= 0){Console.WriteLine($"信息解析完毕!");lastRemainCount = 0;break;}if (sumCount < headSize){Console.WriteLine($"连长度信息都无法解析!{sumCount}");lastRemainCount = sumCount;break;}var length = BitConverter.ToUInt16(readBuff, 0);var remainLength = sumCount - headSize;if (remainLength >= length){//有足够的数据被解析buffIndex += headSize;sumCount -= headSize;var msg = Encoding.UTF8.GetString(readBuff, buffIndex, length);Console.WriteLine($"消息:{msg}");buffIndex += length;sumCount -= length;if(sumCount <= 0){Array.Clear(readBuff);}else{Array.Copy(readBuff, buffIndex, readBuff, 0, sumCount);Array.Clear(readBuff, sumCount, buffIndex);}buffIndex = 0;}else{//没有足够的数据被解析lastRemainCount = sumCount;break;}}
}

测试代码

列举部分测试用例,帮助更好的理解代码,并检验逻辑正确性。

MyNet

 //将字符串转成字节数据,并且会加上长度信息public static byte[] EncodeMsg(string msg){var msgBytes = Encoding.UTF8.GetBytes(msg);//数据源数组var length = (UInt16)msgBytes.Length; //数据长度var lengthBytes = BitConverter.GetBytes(length);//字节数组存储数据长度//拼接数据包var resultBytes = lengthBytes.Concat(msgBytes).ToArray();return resultBytes;}//发送消息public static void SendMsg(params byte[][] msgList){for (var i = 0; i < msgList.Length; i++){if (i != 0){Thread.Sleep(2000);}var data = msgList[i];Console.WriteLine($"发送字节数组:{string.Join("-", data)}");DecodeMsg(data, data.Length);}}//分割字节数组用于测试分包public static void SplitBytes(byte[] sourceArray, int startIndex, out byte[] arrayA, out byte[] arrayB){var partACount = startIndex;var partBCount = sourceArray.Length - partACount;arrayA = new byte[partACount];Array.Copy(sourceArray, 0, arrayA, 0, partACount);arrayB = new byte[partBCount];Array.Copy(sourceArray, partACount, arrayB, 0, partBCount);}

//1、正常:消息1 + 消息2
var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
var msg2 = MyNet.EncodeMsg("Hello World!");
MyNet.SendMsg(msg1, msg2);//2、粘包:[消息1消息2]
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//var msg3 = msg1.Concat(msg2).ToArray();
//MyNet.SendMsg(msg3);//3、粘包+分包:[消息1消息2(A部分)] + 消息2(B部分)
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//MyNet.SplitBytes(msg2, 8, out var msg2A, out var msg2B);
//var msg3 = msg1.Concat(msg2A).ToArray();
//MyNet.SendMsg(msg3, msg2B);//4、分包:消息1 + 消息2(A部分) + 消息2(B部分)
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//MyNet.SplitBytes(msg2, 8, out var msg2A, out var msg2B);
//MyNet.SendMsg(msg1, msg2A, msg2B);//5、分包:消息1(A部分) + 消息1(B部分) + 消息2
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//MyNet.SplitBytes(msg1, 8, out var msg1A, out var msg1B);
//MyNet.SendMsg(msg1A, msg1B, msg2);//6、分包:消息1(A部分) + [消息1(B部分)消息2]
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//MyNet.SplitBytes(msg1, 8, out var msg1A, out var msg1B);
//var msg3 = msg1B.Concat(msg2).ToArray();
//MyNet.SendMsg(msg1A, msg3);//7、分包:消息1(A部分) + 消息1(B部分) + 消息2(A部分) + 消息2(B部分)
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//MyNet.SplitBytes(msg1, 12, out var msg1A, out var msg1B);
//MyNet.SplitBytes(msg2, 8, out var msg2A, out var msg2B);
//MyNet.SendMsg(msg1A, msg1B, msg2A, msg2B);//8
//var msg1 = MyNet.EncodeMsg("锄禾日当午,汗滴禾下土");
//var msg2 = MyNet.EncodeMsg("Hello World!");
//var msg3 = MyNet.EncodeMsg("abcdef123!");
//var msg4 = MyNet.EncodeMsg("Good! 好好学习,天天??");
//var msg5 = MyNet.EncodeMsg("!!!!End");//MyNet.SplitBytes(msg1, 12, out var msg1A, out var msg1B);
//MyNet.SplitBytes(msg2, 7, out var msg2A, out var msg2B);
//MyNet.SplitBytes(msg4, 6, out var msg4A, out var msg4B);//var msg6 = msg1B.Concat(msg2A).ToArray();
//var msg7 = msg2B.Concat(msg3).ToArray();
//MyNet.SendMsg(msg1A, msg6, msg7, msg4A, msg4B, msg5);
http://www.xdnf.cn/news/81703.html

相关文章:

  • 【Web】TGCTF 2025 题解
  • 网络设备智能巡检系统-MCP案例总结
  • 【无人机】使用扩展卡尔曼滤波 (EKF) 算法来处理传感器测量,各传感器的参数设置,高度数据融合、不同传感器融合模式
  • 国产紫光同创FPGA实现SDI视频编解码+图像缩放,基于HSSTHP高速接口,提供2套工程源码和技术支持
  • 万字长文 | Apache SeaTunnel 分离集群模式部署 K8s 集群实践
  • notepad++技巧:查找和替换:扩展 or 正则表达式
  • 什么是 金字塔缩放(Multi-scale Input)
  • 符号速率估计——小波变换法
  • 【在阿里云或其他 CentOS/RHEL 系统上安装和配置 Dante SOCKS5 代理服务】
  • 【MCP Node.js SDK 全栈进阶指南】利用TypeScript-SDK打造高效MCP应用
  • 25.4.22学习总结
  • IOT项目——双轴追光系统
  • # 利用迁移学习优化食物分类模型:基于ResNet18的实践
  • 第十一届机械工程、材料和自动化技术国际会议(MMEAT 2025)
  • 机器人进阶---视觉算法(六)傅里叶变换在图像处理中怎么用
  • hi3516cv610构建音频sample工程代码步骤
  • Spring Boot Actuator 详细使用说明(完整代码与配置)
  • 【C++游戏引擎开发】第21篇:基于物理渲染(PBR)——统计学解构材质与光影
  • 基于华为云 ModelArts 的在线服务应用开发(Requests 模块)
  • 数字IC低功耗设计——基础概念和低功耗设计方法
  • 【Linux】用户权限
  • LangChain 核心模块学习:Chains
  • USB 共享神器 VirtualHere 局域网内远程使用打印机与扫描仪
  • 安宝特科技 | Vuzix Z100智能眼镜+AugmentOS:重新定义AI可穿戴设备的未来——从操作系统到硬件生态,如何掀起无感智能革命?
  • 麒麟系统网络连接问题排查
  • 乐视系列玩机------乐视2 x620红灯 黑砖刷写教程以及新版刷写工具的详细释义
  • C++IO流
  • AI 数字短视频数字人源码开发:多维赋能短视频生态革新​
  • 图像预处理-直方图均衡化
  • 卷积神经网络迁移学习:原理与实践指南