当前位置: 首页 > 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);

相关文章:

  • N8N 官方 MCP 节点实战指南:AI 驱动下的多工具协同应用场景全解析
  • Java—— 正则表达式
  • 算法之回溯法
  • C++初阶——string的使用(上)
  • 词语关系图谱模型
  • QGIS实用功能:加载天地图与下载指定区域遥感影像
  • Python实例题:Python3OpenCV视频转字符动画
  • [Java · 铢积寸累] 基础函数 — 生成随机数 - Math.random() 详解
  • G1 人形机器人硬件构成与接口
  • AI算子开发是什么
  • Agent系统工程实践:Langchain-Chatchat框架定制与优化
  • PostgreSQL认证培训推荐机构
  • 关于el-table可展开行实现懒加载的方案
  • ​​电商系统用户需求报告(示例)
  • Java基础复习(JavaSE进阶)第六章 IO流体系
  • 语音合成(TTS)从零搭建一个完整的TTS系统-第二节-中文转拼音
  • 【Python Web开发】01-Socket网络编程01
  • 【Python爬虫基础篇】--3.cookie和session
  • 乐视系列玩机---乐视1s x500 x501 x502等系列线刷救砖以及刷写第三方twrp 卡刷第三方固件步骤解析
  • 现有一整型数组,a[8] = { 4,8,7,0,3,5,9,1},现使用堆排序的方式原地对该数组进行升序排列。那么在进行第一轮排序结束之后,数组的顺序为?
  • 习近平对双拥工作作出重要指示
  • 高明士︱纪念坚苦卓绝的王寿南先生
  • 神舟二十号载人飞行任务新闻发布会将于4月23日上午召开
  • 日媒:日本公明党党首将访华,并携带石破茂亲笔信
  • 这家企业首次签约参展进博会,为何他说“中资企业没有停止出海的步伐”
  • 水利部启动干旱防御Ⅳ级响应,指导广西陕西抗旱保供保灌