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;
}

区区百行代码却思考很多

相关推荐
有书Show3 小时前
个人IP的塑造方向有哪些?
网络·网络协议·tcp/ip
HHRL-yx3 小时前
C++网络编程 5.TCP套接字(socket)通信进阶-基于多线程的TCP多客户端通信
网络·c++·tcp/ip
iblade7 小时前
网络:TCP序列号和滑动窗口,顺序保证
网络·tcp/ip·php
HHRL-yx9 小时前
C++网络编程 2.TCP套接字(socket)编程详解
网络·c++·tcp/ip
景彡先生9 小时前
STM32以太网开发详解:基于LwIP协议栈实现TCP/UDP通信(附网络摄像头案例)
网络·stm32·tcp/ip
R-G-B9 小时前
【27】MFC入门到精通——MFC 修改用户界面登录IP IP Address Control
tcp/ip·ui·mfc·mfc 用户界面登录·mfc ip登录·mfc address登录
之歆10 小时前
Python-TCP编程-UDP编程-SocketServer-IO各种概念及多路复用-asyncio-学习笔记
python·tcp/ip·udp
小徐不徐说1 天前
超详细讲解:TCP / UDP / HTTP / HTTPS 四种常见协议
c++·网络协议·tcp/ip·http·https·udp·网络编程
erxij1 天前
【游戏引擎之路】登神长阶(十八):3天制作Galgame引擎《Galplayer》——无敌之道心
游戏引擎