欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > 网络开发基础(游戏)之 粘包分包

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

2025/4/23 6:52:49 来源:https://blog.csdn.net/qq_44809934/article/details/147359911  浏览:    关键词:网络开发基础(游戏)之 粘包分包

 粘(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);

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词