Unity网络通信的插件分享,及TCP粘包分包问题处理

Netly插件分享

GitHub地址

我觉得它简单、高效、全面。能够快速开发,官网也提供了每种通信的样例。

TCP粘包分包处理

思路:

设置缓冲区,将新数据追加到缓冲区,然后再从缓冲区中,根据拟定的数据格式进行解析。
数据格式如下:

数据流格式:数据长度 + 数据包类型 + 数据内容

数据长度:数据包类型 + 数据内容的长度

数据包类型: 定义的数据包标志

数据内容:整个数据内容采用json字符串形式,结尾增加一个识别字节0。
客户端发送消息:

csharp 复制代码
    void sendMessage<T>(uint type, T msg)
    {
        try
        {
            // 序列化消息
            string json = JsonConvert.SerializeObject(msg);
            Debug.Log($"发送的json: {json}");
            byte[] msgBytes = Encoding.UTF8.GetBytes(json);

            // 计算总长度(消息类型 + 消息内容 + 结尾的0)
            int totalLength = sizeof(uint) + msgBytes.Length + 1; // 类型长度 + 消息长度 + 1(结尾的0)

            // 使用 MemoryStream 和 BinaryWriter 构建字节数组
            using (var stream = new MemoryStream())
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(totalLength); // 写入总长度
                writer.Write(type);       // 写入消息类型
                writer.Write(msgBytes);   // 写入消息内容
                writer.Write((byte)0);    // 写入结尾的0

                // 发送数据
                client.To.Data(stream.ToArray());
            }
        }
        catch (Exception ex)
        {
            Debug.LogError("发送消息失败: " + ex.Message);
        }
    }

客户端接收消息:

csharp 复制代码
private Dictionary<int, Action<string>> _messageHandlers = new();//处理消息

int erorrCount;
private Memory<byte> _receiveMemory = Memory<byte>.Empty; //缓冲区
void msgData(byte[] bytes)
{
    if (bytes == null || bytes.Length == 0)
    {
        Debug.LogError("无效数据包:空数据");
        return;
    }
    //string hexResult = "";
    //foreach (byte b in bytes)
    //{
    //    hexResult += b.ToString("X2") + " ";
    //}
    //Debug.Log($"消息字节: {hexResult}");

    #region Memory缓冲区
    // 将新数据追加到缓冲区
    _receiveMemory = CombineMemory(_receiveMemory, bytes.AsMemory());

    try
    {
        while (_receiveMemory.Length >= 8)
        {
            var bufferSpan = _receiveMemory.Span;
            int contentLength = BitConverter.ToInt32(bufferSpan.Slice(0, 4));//数据长度
            int type = BitConverter.ToInt32(bufferSpan.Slice(4, 4));// 数据类型
            if (type > 100)
            {
                // 找到第一个 0 字节的位置
                int zeroIndex = _receiveMemory.Span.IndexOf((byte)0);
                if (zeroIndex >= 0)
                {
                    _receiveMemory = _receiveMemory.Slice(zeroIndex + 1); // 移除到 0 字节之后
                    Debug.LogError($"数据包类型错误: {type},移除到 0 字节之后");
                }
                else
                {
                    _receiveMemory = Memory<byte>.Empty; // 如果没有 0 字节,清空缓冲区
                    Debug.LogError($"数据包类型错误: {type},没有 0 字节,清空缓冲区");
                }
                break;
            }
            int totalPacketLength = 4 + contentLength;
            int msgLength = contentLength - 5;//(减4减1)
            try
            {
                //检查是否收到完整的数据包
                if (_receiveMemory.Length >= totalPacketLength)
                {
                    // 验证结尾字节是否为0
                    if (bufferSpan[totalPacketLength - 1] != 0)
                    {
                        _receiveMemory = Memory<byte>.Empty;
                        Debug.LogError("清除缓冲区,数据包格式错误: 缺少结尾0字节");
                        break;
                    }

                    var msgMemory = _receiveMemory.Slice(8, msgLength);
                    string msg = Encoding.UTF8.GetString(msgMemory.Span);
                    Debug.Log($"收到的消息:{msg}");
                    if (_messageHandlers.TryGetValue(type, out var handler))
                    {
                        try
                        {
                            handler(msg);//业务处理
                        }
                        catch (Exception e)
                        {
                            Debug.LogError($"业务逻辑处理报错:{e.Message}");
                        }
                    }
                    else
                    {
                        //Debug.LogError($"未知的消息类型: {type}");
                    }
                    //Debug.Log("从缓冲区移除已处理的数据");
                    _receiveMemory = _receiveMemory.Slice(totalPacketLength);
                    erorrCount = 0;
                }
                else
                {
                    Debug.LogError($"数据不完整,类型:{type},预期长度: {totalPacketLength}, 实际长度: {_receiveMemory.Length}");
                    erorrCount++;
                    if (erorrCount >= 10)
                    {
                        erorrCount = 0;
                        _receiveMemory = Memory<byte>.Empty;
                        Debug.LogError("清除缓冲区,数据不完整超过10次");
                    }
                    break;
                }
            }
            catch (Exception e)
            {
                Debug.LogError($"清除缓冲区,处理数据包时出错: {e.Message}");
                _receiveMemory = Memory<byte>.Empty;
            }
        }

    }
    catch (Exception e)
    {
        Debug.LogError($"清除缓冲区,消息处理异常: {e.Message}");
        _receiveMemory = Memory<byte>.Empty;
    }

    #endregion
}

// 合并两个 Memory<byte>(模拟 List<byte>.AddRange)
private Memory<byte> CombineMemory(Memory<byte> a, Memory<byte> b)
{
    if (a.IsEmpty) return b;
    if (b.IsEmpty) return a;

    var newBuffer = new byte[a.Length + b.Length];
    a.CopyTo(newBuffer.AsMemory(0, a.Length));
    b.CopyTo(newBuffer.AsMemory(a.Length, b.Length));
    return newBuffer;
}

区区百行代码却思考很多

相关推荐
2503_924806854 小时前
动态IP使用中 报错407 怎么办???
服务器·tcp/ip·php
歪歪1007 小时前
什么是TCP/UDP/HTTP?
开发语言·网络·网络协议·tcp/ip·http·udp
爱吃小胖橘11 小时前
Unity-动画子状态机
3d·unity·c#·游戏引擎
SmalBox12 小时前
【光照】[物理模型]中的[BRDF]是什么?
unity·渲染
玉龙202515 小时前
使用虚幻引擎|UE5制作自动开关门
ue5·游戏引擎·虚幻·虚幻引擎教程
ellis197016 小时前
toLua[三] Examples 02_ScriptsFromFile分析
unity
oLingXi1218 小时前
Unity开发CI/CD工具Jenkins的安装(Windows10)
运维·unity·ci/cd·jenkins
SmalBox1 天前
【光照】Unity中的[物理模型]PBR
unity·渲染
roman_日积跬步-终至千里1 天前
【系统架构设计(35)】TCP/IP协议族详解
网络协议·tcp/ip·系统架构
#include<菜鸡>1 天前
AXI_CAN IP 简单使用。(仿真、microblaze)
网络协议·tcp/ip·fpga开发