深入剖析 Fantasy 框架的网络数据包解析机制:从抽象基类到多协议适配

深入剖析 Fantasy 框架的网络数据包解析机制

引言:数据包解析的核心价值

网络通信是分布式系统的核心支柱,而数据包解析则是网络模块的 "神经中枢"------ 它负责将字节流转换为可执行的业务逻辑,同时将业务数据编码为可传输的字节序列。 Fantasy 框架作为一款面向分布式场景的开发框架,其网络数据包解析机制经过精心设计,既保证了协议适配的灵活性,又兼顾了高性能与可扩展性。

本文将从源码层面深度剖析 Fantasy 框架的数据包解析机制,围绕抽象基类设计、具体解析器实现、工厂模式适配、多协议兼容等核心维度,拆解从字节流到业务对象的完整生命周期。通过对 15+ 核心类、3000+ 行关键代码的逐段分析,揭示框架如何解决分包粘包、协议兼容、内存高效管理等分布式网络开发中的经典问题。

框架设计概览:数据包解析的整体架构

Fantasy 框架的数据包解析机制采用 "抽象定义 - 具体实现 - 工厂调度" 的三层架构,通过面向接口编程实现了解析逻辑与业务逻辑的解耦,同时为多协议适配提供了灵活的扩展点。

核心组件关系图谱

从代码结构来看,解析机制的核心组件可归纳为以下四类:

  • 抽象层 :以 APacketParser 为核心,定义解析器的通用接口;APackInfo 定义解析结果的通用载体。
  • 实现层 :包括 BufferPacketParserReadOnlyMemoryPacketParserCircularBufferPacketParser 等具体解析器,分别适配不同的网络协议与内存模型。
  • 工厂层PacketParserFactory 负责根据网络类型(Inner/Outer)和协议类型创建对应的解析器实例,屏蔽具体实现细节。
  • 辅助层Packet 结构体定义数据包格式常量;OpCode 相关类型负责协议编号的编码与解码。

这些组件的协作流程可概括为:
网络数据到达工厂创建解析器解析器执行 UnPack 解码APackInfo 承载解析结果业务逻辑处理解析器执行 Pack 编码数据发送

抽象层双核心:解析器契约与结果载体的协同设计

抽象基类 APacketParser:解析器的 "宪法性文件"

APacketParser 作为所有解析器的基类,定义了数据包解析的核心契约。其源码位于 Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs,核心代码如下:

csharp 复制代码
using System;
using System.Buffers;
using System.IO;
using Fantasy.Network.Interface;
using Fantasy.Serialize;

#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

namespace Fantasy.PacketParser.Interface
{
    /// <summary>
    /// 抽象的包解析器基类,用于解析网络通信数据包。
    /// </summary>
    public abstract class APacketParser : IDisposable
    {
        internal Scene Scene;
        internal ANetwork Network;
        internal MessageDispatcherComponent MessageDispatcherComponent;
        protected bool IsDisposed { get; private set; }
        public abstract MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message);
        public virtual void Dispose()
        {
            IsDisposed = true;
            Scene = null;
            MessageDispatcherComponent = null;
        }
    }
}
接口定义的核心契约:从功能约定到协作逻辑
  • 生命周期管理 :实现 IDisposable 接口,通过 IsDisposed 状态标记跟踪实例是否已释放;在 Dispose 方法中主动清理关联的 Scene(场景实例)和 MessageDispatcherComponent(消息分发组件),避免无效引用导致的内存泄漏,确保解析器资源在生命周期结束后可被正确回收。
  • 核心方法契约Pack 方法被定义为抽象方法,强制所有子类必须实现数据包的编码逻辑,该方法需将业务消息(IMessage 实例)转换为可网络传输的字节流(MemoryStreamBuffer),同时携带 rpcId(远程调用标识)和 routeId(路由标识)等元数据;通过抽象设计保证了所有解析器的功能一致性,又为不同协议(Tcp/Kcp)的差异化编码逻辑预留了实现空间。
  • 上下文关联机制 :通过 SceneNetwork 成员变量关联当前解析器所属的场景和网络实例,使解析器能够访问框架全局资源(如消息分发器、内存池、协议配置等);这种上下文绑定是解析器与框架其他组件协作的基础,例如编码时需从内存池获取 MemoryStreamBuffer,解码后需通过消息分发器路由到对应处理逻辑。

APacketParser 的设计核心是 "最小接口 + 最大灵活性",仅定义必要的通用接口,将具体编码 / 解码逻辑交给子类,为不同协议的适配预留足够空间,形成完整的协作链路。

Pack 方法的参数设计:通信模型的映射

Pack 方法作为数据包编码的入口,其参数列表直接映射了框架的通信模型,对其深入拆解可知:

  • ref uint rpcId:引用传递的 RPC 标识。在请求 - 响应模式中,客户端发送请求时会生成一个唯一 rpcId,服务器响应时携带相同标识,以此实现 "请求 - 响应" 的配对。ref 关键字的使用,允许方法内部自动生成 rpcId,兼顾灵活性与性能。
  • ref long routeId:路由标识。在分布式系统中,消息可能需要跨服务器传递(如玩家从网关服务器路由到战斗服务器),routeId 包含目标服务器的地址信息。同样使用 ref 传递,支持方法内部根据消息类型自动填充路由信息。
  • MemoryStreamBuffer memoryStream:可复用的内存流缓冲区。传统 MemoryStream 每次创建都会分配新内存,而 MemoryStreamBuffer 来自框架的内存池,通过复用减少 GC 压力。
  • IMessage message:待编码的业务消息。框架要求所有业务消息实现 IMessage 接口,该接口定义了 OpCode() 方法(返回消息类型标识),为后续序列化与路由提供依据。
  • Upack方法的多样性:Upack 方法与 Pack 方法不同,Upack 方法由于输入数据类型的不同而呈现出多样性,因此在 APacketParser 基类中,并不提供 Upack 的标准声明,由各子类根据自身特性定义。

数据包载体 APackInfo:解析结果的 "标准容器"

APacketParser 配套的是 APackInfo 抽象类,它定义了解析后数据包的通用结构,源码位于 Runtime/Core/Network/Message/PacketParser/Interface/APackInfo.cs

ini 复制代码
using System;
using System.IO;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.Serialize;

// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

namespace Fantasy.PacketParser.Interface
{
    public abstract class APackInfo : IDisposable
    {
        internal ANetwork Network;
        
        public uint RpcId;
        public long RouteId;
        public long PackInfoId;
        public bool IsDisposed;
        private uint _protocolCode;
        
        public uint ProtocolCode
        {
            get => _protocolCode;
            set
            {
                _protocolCode = value;
                OpCodeIdStruct = value;
            }
        }
        public OpCodeIdStruct OpCodeIdStruct { get; private set; }
        public MemoryStreamBuffer MemoryStream { get; protected set; }
        public abstract object Deserialize(Type messageType);
        public abstract MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0);
        public virtual void Dispose()
        {
            if (IsDisposed)
            {
                return;
            }
            
            RpcId = 0;
            RouteId = 0;
            PackInfoId = 0;
            ProtocolCode = 0;
            _protocolCode = 0;
            OpCodeIdStruct = default;
            
            if (MemoryStream != null)
            {
                Network.MemoryStreamBufferPool.ReturnMemoryStream(MemoryStream);
                MemoryStream = null;
            }
            
            IsDisposed = true;
            Network = null;
        }
    }
}
核心属性与功能解析:消息元信息的标准化载体

核心元数据承载 :通过RpcId(远程调用唯一标识)、RouteId(分布式路由标识)、ProtocolCode(协议编号)三大核心字段,存储数据包的控制层信息。这些元数据是分布式系统中实现消息路由分发、远程调用回调关联、跨节点通信的基础支撑。 协议编号自动解析ProtocolCode属性通过内部关联的OpCodeIdStruct,自动完成协议类型(如 ProtoBuf/Bson)、消息角色(如请求 / 响应)、业务索引的解析,为后续反序列化操作提供明确的类型依据,实现 "协议编号→解析规则" 的自动化映射。 池化内存管理 :抽象RentMemoryStream方法统一内存流获取逻辑(通常从内存池租赁),配合Dispose方法将内存流归还至池化管理,通过复用内存资源减少 GC 频率,在高并发场景下显著提升性能。 反序列化接口抽象 :定义Deserialize抽象方法,负责将内存流中的字节数据转换为具体业务对象(如请求 / 响应消息)。具体实现由子类根据协议场景(如 Inner 内部协议 / Outer 外部协议)定制,实现解析逻辑与协议类型的解耦。

APackInfoAPacketParser 是 "生产者 - 消费者" 关系:解析器(APacketParser)生产 APackInfo 实例,业务逻辑消费 APackInfo 中的数据 ------ 这种设计使解析逻辑与业务逻辑完全解耦。

数据包格式定义:字节级别的协议约定

数据包解析的前提是统一的格式约定。 Fantasy 框架通过 Packet 结构体定义了内网(Inner)和外网(Outer)数据包的格式规范,确保发送方与接收方对字节流的解析逻辑一致。

Packet 结构体:数据包的 "格式说明书"

Packet 结构体位于 Runtime/Core/Network/Message/PacketParser/Packet.cs,定义了数据包各部分的长度、偏移量等常量:

csharp 复制代码
namespace Fantasy.PacketParser
{
    /// <summary>
    /// 提供关于消息包的常量定义。
    /// </summary>
    public struct Packet
    {
        /// <summary>
        /// 消息体最大长度
        /// </summary>
        public const int PacketBodyMaxLength = ushort.MaxValue * 16;
        /// <summary>
        /// 消息体长度在消息头占用的长度
        /// </summary>
        public const int PacketLength = sizeof(int);
        /// <summary>
        /// 协议编号在消息头占用的长度
        /// </summary>
        public const int ProtocolCodeLength = sizeof(uint);
        /// <summary>
        /// RouteId长度
        /// </summary>
        public const int PacketRouteIdLength = sizeof(long);
        /// <summary>
        /// RpcId在消息头占用的长度
        /// </summary>
        public const int RpcIdLength = sizeof(uint);
        /// <summary>
        /// OuterRPCId所在的位置
        /// </summary>
        public const int OuterPacketRpcIdLocation = PacketLength + ProtocolCodeLength;
        /// <summary>
        /// InnerRPCId所在的位置
        /// </summary>
        public const int InnerPacketRpcIdLocation = PacketLength + ProtocolCodeLength;
        /// <summary>
        /// RouteId所在的位置
        /// </summary>
        public const int InnerPacketRouteRouteIdLocation = PacketLength + ProtocolCodeLength + RpcIdLength;
        /// <summary>
        /// 外网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteId长度)
        /// </summary>
        public const int OuterPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength;
        /// <summary>
        /// 内网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteId长度)
        /// </summary>
        public const int InnerPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength;
    }
}
核心常量与功能解析:数据包结构的标准化契约
  • 消息体长度管控PacketBodyMaxLength 设定消息体最大长度为 ushort.MaxValue * 16(1,048,560 字节),通过硬性限制避免超大数据包对网络传输效率和内存资源的过度占用,平衡通信灵活性与系统稳定性。
  • 头部结构的原子化定义 :将数据包头部拆解为基础长度与相对偏移两类常量,构建完整的结构坐标体系 ------ 基础长度上,PacketLength(4 字节,消息体长度字段)、ProtocolCodeLength(4 字节,协议编号字段)、RpcIdLength(4 字节,远程调用标识字段)、PacketRouteIdLength(8 字节,路由标识字段,即通过如下的 OpCode.Create 方法生成的 uint 类型协议编号(本质是 OpCodeIdStruct 隐式转换的结果))明确了各元数据的存储占用;相对偏移上,OuterPacketRpcIdLocationInnerPacketRpcIdLocation 均定义为 PacketLength + ProtocolCodeLength(8 字节),标识 RpcId 字段在头部中的相对偏移量(即从头部第一个字节开始,经过前两个字段后,第 8 字节处为 RpcId 的起始点),而 InnerPacketRouteRouteIdLocation 单独定义为 PacketLength + ProtocolCodeLength + RpcIdLength(12 字节),专门标记内网消息中 RouteId 的相对偏移量。
  • 内外网头部的场景化适配OuterPacketHeadLengthInnerPacketHeadLength 数值上均为 20 字节(4+4+4+8),保持头部总长度一致以复用基础解析逻辑;但两者的设计差异体现了场景需求的不同 ------ 内网因涉及服务器间分布式通信,路由逻辑复杂,通过 InnerPacketRouteRouteIdLocation 精确标记 RouteId 位置以支持跨节点消息分发;而外网通信多为客户端与目标服务器的直接交互,便无需单独定义 RouteId 偏移常量。
  • 解析流程的基准坐标 :这些常量共同构成数据包编码 / 解码的 "度量衡"------ 编码时按此结构组装头部,确保接收方能够精准解析;解码时依据相对偏移量直接定位并提取长度、协议编号等关键信息,为后续反序列化(基于协议编号)和路由分发(基于 RpcId/RouteId)提供基础,是连接 "原始字节流" 与 "业务对象" 的结构性桥梁。

OpCodeIdStruct:三元复合的协议标识载体

协议编号(ProtocolCode)是数据包的 "身份证"------ 通过 32 位整数的精准位分割,同时承载三类核心信息:高 5 位为 Protocol(消息分类类型,如内网 / 外网等,对应 OpCodeType 定义),中 4 位为 OpCodeProtocolType(序列化协议类型,如 ProtoBuf/Bson,对应 OpCodeProtocolType 定义),低 23 位为 Index(具体消息索引,确保同类消息的唯一性)。

Fantasy 框架通过 OpCodeIdStruct 实现了 32 位整数的多维度编码,将消息的 "分类属性""解析方式""唯一标识" 紧凑封装其中。 源码位于 Runtime/Core/Network/Message/PacketParser/OpCode.cs

ini 复制代码
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
namespace Fantasy.Network
{
    public struct OpCodeIdStruct
    {
        // OpCodeIdStruct:5 + 4 + 23 = 32
        // +-------------------------+-------------------------------------------+-----------------------------+
        // |  protocol(5) 最多31种类型 | OpCodeProtocolType(4) 最多15种不同的网络协议 | Index(23) 最多8388607个协议  |
        // +-------------------------+-------------------------------------------+-----------------------------+
        public uint OpCodeProtocolType { get; private set; }
        public uint Protocol { get; private set; }
        public uint Index { get; private set; }
        
        public OpCodeIdStruct(uint opCodeProtocolType, uint protocol, uint index)
        {
            OpCodeProtocolType = opCodeProtocolType;
            Protocol = protocol;
            Index = index;
        }

        public static implicit operator uint(OpCodeIdStruct opCodeIdStruct)
        {
            var result = opCodeIdStruct.Index;
            result |= opCodeIdStruct.OpCodeProtocolType << 23;
            result |= opCodeIdStruct.Protocol << 27;
            return result;
        }

        public static implicit operator OpCodeIdStruct(uint opCodeId)
        {
            var opCodeIdStruct = new OpCodeIdStruct()
            {
                Index = opCodeId & 0x7FFFFF
            };
            opCodeId >>= 23;
            opCodeIdStruct.OpCodeProtocolType = opCodeId & 0xF;
            opCodeId >>= 4;
            opCodeIdStruct.Protocol = opCodeId & 0x1F;
            return opCodeIdStruct;
        }
    }

    public static class OpCodeProtocolType
    {
        public const uint Bson = 1; 
        public const uint ProtoBuf = 0;
    }

    public static class OpCodeType
    {
        public const uint OuterMessage = 1; 
        public const uint OuterRequest = 2;
        public const uint OuterResponse = 3;
        
        public const uint InnerMessage  = 4;
        public const uint InnerRequest  = 5;
        public const uint InnerResponse = 6;
        
        public const uint InnerRouteMessage = 7;
        public const uint InnerRouteRequest = 8;
        public const uint InnerRouteResponse = 9;
        
        public const uint OuterAddressableMessage = 10;
        public const uint OuterAddressableRequest = 11;
        public const uint OuterAddressableResponse = 12;
        
        public const uint InnerAddressableMessage = 13;
        public const uint InnerAddressableRequest = 14;
        public const uint InnerAddressableResponse = 15;
        
        public const uint OuterCustomRouteMessage = 16;
        public const uint OuterCustomRouteRequest = 17;
        public const uint OuterCustomRouteResponse = 18;
        
        public const uint OuterRoamingMessage = 19;
        public const uint OuterRoamingRequest = 20;
        public const uint OuterRoamingResponse = 21;
        
        public const uint InnerRoamingMessage = 22;
        public const uint InnerRoamingRequest = 23;
        public const uint InnerRoamingResponse = 24;
        
        public const uint OuterPingRequest = 30;
        public const uint OuterPingResponse = 31;
    }

    public static class OpCode
    {
        public static readonly uint BenchmarkMessage = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterMessage, 8388607);
        public static readonly uint BenchmarkRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterRequest, 8388607);
        public static readonly uint BenchmarkResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterResponse, 8388607);
        public static readonly uint PingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingRequest, 1);
        public static readonly uint PingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingResponse, 1);
        public static readonly uint DefaultResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerResponse, 1);
        public static readonly uint DefaultRouteResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 7);
        public static readonly uint AddressableAddRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 1);
        public static readonly uint AddressableAddResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 1);
        public static readonly uint AddressableGetRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 2);
        public static readonly uint AddressableGetResponse = Create(OpCodeProtocolType.ProtoBuf,OpCodeType.InnerRouteResponse,2);
        public static readonly uint AddressableRemoveRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 3);
        public static readonly uint AddressableRemoveResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 3);
        public static readonly uint AddressableLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 4);
        public static readonly uint AddressableLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 4);
        public static readonly uint AddressableUnLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 5);
        public static readonly uint AddressableUnLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 5);
        public static readonly uint LinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 6);
        public static readonly uint LinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 6);
        public static readonly uint UnLinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 8);
        public static readonly uint UnLinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 8);
        public static readonly uint LockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 9);
        public static readonly uint LockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 9);
        public static readonly uint UnLockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 10);
        public static readonly uint UnLockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 10);
        public static readonly uint GetTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 11);
        public static readonly uint GetTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 11);
        
        public static readonly uint TransferTerminusRequest = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteRequest, 1);
        public static readonly uint TransferTerminusResponse = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteResponse, 1);
        
        public static uint Create(uint opCodeProtocolType, uint protocol, uint index)
        {
            return new OpCodeIdStruct(opCodeProtocolType, protocol, index);
        }
    }
}
核心属性与功能解析:依托隐式转换的消息标识复合载体
  • 三元信息聚合 :通过 OpCodeProtocolType(4 位,序列化协议类型)、Protocol(5 位,消息分类类型)、Index(23 位,具体消息索引)三个属性,将消息的 "序列化方式""分类属性""唯一索引" 聚合为统一标识,形成分布式系统中消息的 "完整身份凭证"。
  • 32 位紧凑存储:采用 "5 位 + 4 位 + 23 位" 的位分配策略,在单 32 位整数中实现多维度信息的高密度存储,支持 32 种消息分类、16 种序列化协议、838 万 + 具体消息,兼顾扩展性与传输效率。
  • implicit 隐式转换机制 :通过 public static implicit operator uint(OpCodeIdStruct)public static implicit operator OpCodeIdStruct(uint) 两个隐式转换运算符,实现结构体与 32 位整数的无缝双向转换 ------ 合并时自动通过位运算组装三元信息为整数,拆分时自动从整数中提取各字段,无需显式调用转换方法,既简化了代码使用,又保证了转换操作的高效性(零内存分配)。
  • 标识体系核心支撑 :作为 OpCode 类创建具体消息标识的基础组件,结合 OpCodeProtocolType(序列化协议定义)和 OpCodeType(消息分类定义),通过隐式转换生成的 32 位整数可直接用于消息传输、解析器选择和路由分发,贯穿消息处理全流程。

具体解析器实现:从内存模型到协议适配

Fantasy 框架针对不同的内存模型和网络协议,提供了三种核心解析器:BufferPacketParser(字节数组适配)、ReadOnlyMemoryPacketParser(只读内存块适配)、CircularBufferPacketParser(循环缓冲区适配)。每种解析器都有其特定的应用场景和实现细节。且均依赖 ISerialize 接口完成数据转换。

BufferPacketParser:字节数组的高效解析器

BufferPacketParser 是针对字节数组(byte [])设计的解析器,主要用于 KCP 协议(框架注释明确说明 "KCP 出来的就是一个完整的包,所以可以一次性全部解析出来")。其源码位于 Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs

csharp 复制代码
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Fantasy.Helper;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.PacketParser.Interface;
using Fantasy.Serialize;

// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
namespace Fantasy.PacketParser
{
    /// <summary>
    /// BufferPacketParser消息格式化器抽象类
    /// 这个不会用在TCP协议中、因此不用考虑分包和粘包的问题。
    /// 目前这个只会用在KCP协议中、因为KCP出来的就是一个完整的包、所以可以一次性全部解析出来。
    /// 如果是用在其他协议上可能会出现问题。
    /// </summary>
    public abstract class BufferPacketParser : APacketParser
    {
        protected uint RpcId;
        protected long RouteId;
        protected uint ProtocolCode;
        protected int MessagePacketLength;
        public override void Dispose()
        {
            RpcId = 0;
            RouteId = 0;
            ProtocolCode = 0;
            MessagePacketLength = 0;
            base.Dispose();
        }
        /// <summary>
        /// 解包方法
        /// </summary>
        /// <param name="buffer">buffer</param>
        /// <param name="count">count</param>
        /// <param name="packInfo">packInfo</param>
        /// <returns></returns>
        public abstract bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo);
    }
#if FANTASY_NET
    /// <summary>
    /// 服务器之间专用的BufferPacketParser消息格式化器
    /// </summary>
    public sealed class InnerBufferPacketParser : BufferPacketParser
    {
        /// <summary>
        /// <see cref="BufferPacketParser.UnPack"/>
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="count"></param>
        /// <param name="packInfo"></param>
        /// <returns></returns>
        /// <exception cref="ScanException"></exception>
        public override unsafe bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo)
        {
            packInfo = null;

            if (buffer.Length < count)
            {
                throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}");
            }
            
            if (count < Packet.InnerPacketHeadLength)
            {
                // 如果内存资源中的数据长度小于内部消息头的长度,无法解析
                return false;
            }
            
            fixed (byte* bufferPtr = buffer)
            {
                MessagePacketLength = *(int*)bufferPtr;

                if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength)
                {
                    // 检查消息体长度是否超出限制
                    throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
                }
                
                ProtocolCode = *(uint*)(bufferPtr + Packet.PacketLength);
                RpcId = *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation);
                RouteId = *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation);
            }
            
            packInfo = InnerPackInfo.Create(Network);
            packInfo.RpcId = RpcId;
            packInfo.RouteId = RouteId;
            packInfo.ProtocolCode = ProtocolCode;
            packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count);
            return true;
        }
        
        public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
        {
            return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream)
        {
            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
                *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
            }
            
            return memoryStream;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message)
        {
            var memoryStreamLength = 0;
            var messageType = message.GetType();
            var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
            OpCodeIdStruct opCodeIdStruct = message.OpCode();
            memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);

            if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                serializer.Serialize(messageType, message, memoryStream);
                memoryStreamLength = (int)memoryStream.Position;
            }
            else
            {
                Log.Error($"type:{messageType} Does not support processing protocol");
            }

            var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
            var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength;
            
            if (packetBodyCount == 0)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packetBodyCount = -1;
            }
            
            if (packetBodyCount > Packet.PacketBodyMaxLength)
            {
                // 检查消息体长度是否超出限制
                throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
            }

            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(int*)bufferPtr = packetBodyCount;
                *(uint*)(bufferPtr + Packet.PacketLength) = opCode;
                *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
                *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
            }
            
            return memoryStream;
        }
    }
#endif
    /// <summary>
    /// 客户端和服务器之间专用的BufferPacketParser消息格式化器
    /// </summary>
    public sealed class OuterBufferPacketParser : BufferPacketParser
    {
        /// <summary>
        /// <see cref="BufferPacketParser.UnPack"/>
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="count"></param>
        /// <param name="packInfo"></param>
        /// <returns></returns>
        /// <exception cref="ScanException"></exception>
        public override unsafe bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo)
        {
            packInfo = null;

            if (buffer.Length < count)
            {
                throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}");
            }
            
            if (count < Packet.OuterPacketHeadLength)
            {
                // 如果内存资源中的数据长度小于内部消息头的长度,无法解析
                return false;
            }
            
            fixed (byte* bufferPtr = buffer)
            {
                MessagePacketLength =  *(int*)bufferPtr;

                if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength)
                {
                    // 检查消息体长度是否超出限制
                    throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
                }

                ProtocolCode = *(uint*)(bufferPtr + Packet.PacketLength);
                RpcId = *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation);
            }
            
            packInfo = OuterPackInfo.Create(Network);
            packInfo.RpcId = RpcId;
            packInfo.ProtocolCode = ProtocolCode;
            packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count);
            return true;
        }
        
        public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
        {
            return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream)
        {
            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
            }
            
            return memoryStream;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message)
        {
            var memoryStreamLength = 0;
            var messageType = message.GetType();
            var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
            OpCodeIdStruct opCodeIdStruct = message.OpCode();
            memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
            
            if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                serializer.Serialize(messageType, message, memoryStream);
                memoryStreamLength = (int)memoryStream.Position;
            }
            else
            {
                Log.Error($"type:{messageType} Does not support processing protocol");
            }
            
            var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
            var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;
            
            if (packetBodyCount == 0)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packetBodyCount = -1;
            }
            
            if (packetBodyCount > Packet.PacketBodyMaxLength)
            {
                // 检查消息体长度是否超出限制
                throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
            }

            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(int*)bufferPtr = packetBodyCount;
                *(uint*)(bufferPtr + Packet.PacketLength) = opCode;
                *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
            }
            
            return memoryStream;
        }
    }
    /// <summary>
    /// Webgl专用的客户端和服务器之间专用的BufferPacketParser消息格式化器
    /// </summary>
    public sealed class OuterWebglBufferPacketParser : BufferPacketParser
    {
        /// <summary>
        /// <see cref="BufferPacketParser.UnPack"/>
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="count"></param>
        /// <param name="packInfo"></param>
        /// <returns></returns>
        /// <exception cref="ScanException"></exception>
        public override bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo)
        {
            packInfo = null;

            if (buffer.Length < count)
            {
                throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}");
            }
            
            if (count < Packet.OuterPacketHeadLength)
            {
                // 如果内存资源中的数据长度小于内部消息头的长度,无法解析
                return false;
            }
            
            MessagePacketLength = BitConverter.ToInt32(buffer, 0);
            
            if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength)
            {
                // 检查消息体长度是否超出限制
                throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
            }
            
            ProtocolCode = BitConverter.ToUInt32(buffer, Packet.PacketLength);
            RpcId = BitConverter.ToUInt32(buffer, Packet.OuterPacketRpcIdLocation);
            
            packInfo = OuterPackInfo.Create(Network);
            packInfo.RpcId = RpcId;
            packInfo.ProtocolCode = ProtocolCode;
            packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count);
            return true;
        }
        
        public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
        {
            return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream)
        {
            var buffer = memoryStream.GetBuffer().AsSpan();
#if FANTASY_NET
            MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), in rpcId);
#endif
#if FANTASY_UNITY
            MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), ref rpcId);
#endif
            return memoryStream;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message)
        {
            var memoryStreamLength = 0;
            var messageType = message.GetType();
            var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.UnPack);
            OpCodeIdStruct opCodeIdStruct = message.OpCode();
            memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
            
            if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                serializer.Serialize(messageType, message, memoryStream);
                memoryStreamLength = (int)memoryStream.Position;
            }
            else
            {
                Log.Error($"type:{messageType} Does not support processing protocol");
            }
            
            var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
            var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;
            
            if (packetBodyCount == 0)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packetBodyCount = -1;
            }
            
            if (packetBodyCount > Packet.PacketBodyMaxLength)
            {
                // 检查消息体长度是否超出限制
                throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
            }
            
            var buffer = memoryStream.GetBuffer().AsSpan();
#if FANTASY_NET
            MemoryMarshal.Write(buffer, in packetBodyCount);
            MemoryMarshal.Write(buffer.Slice(Packet.PacketLength, sizeof(uint)), in opCode);
            MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), in rpcId);
#endif
#if FANTASY_UNITY
            MemoryMarshal.Write(buffer, ref packetBodyCount);
            MemoryMarshal.Write(buffer.Slice(Packet.PacketLength, sizeof(uint)), ref opCode);
            MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), ref rpcId);
#endif
            return memoryStream;
        }
    }
}
BufferPacketParser 的基础构成:基类继承关系与核心成员定义

BufferPacketParser 继承自抽象基类 APacketParser,延续了数据包解析器的基础契约。它重写的 Dispose 方法在释放基类资源的同时,额外清理了自身维护的状态变量(RpcIdRouteId 等),形成完整的生命周期管理链路。

BufferPacketParser 类内定义了关键状态变量:RpcId(远程调用标识)、RouteId(路由标识)、ProtocolCode(协议编号)、MessagePacketLength(消息包长度),用于暂存解析过程中的核心元数据;抽象方法 UnPack 则定义了字节数组解析的入口契约,要求子类实现具体的解包逻辑(输入字节数组与长度引用,输出解析结果载体 APackInfo)。

InnerBufferPacketParser 功能解析:内网 KCP 协议的编解码全链路

内网通信(服务器间)的解包逻辑由 InnerBufferPacketParser 实现。

解包流程(UnPack):从字节流到消息载体的转换

参数校验 :首先验证缓冲区实际长度是否小于待解析数据长度(count),不满足则抛出异常以避免无效内存访问;若数据总长度不足内网消息头固定长度(20 字节,即 Packet.InnerPacketHeadLength),判定为数据不完整,直接返回 false 终止解析,确保后续操作基于完整的头部基础。

unsafe 代码优化 :通过 fixed 关键字固定字节数组的内存地址(防止被垃圾回收器移动),将缓冲区转换为非托管指针(byte*)直接操作内存。这种方式规避了托管代码默认的边界检查(如索引越界校验)和类型转换开销,显著提升解析效率。

头部信息提取 :基于指针从固定偏移位置精准读取关键信息:从 0 偏移(缓冲区起始)读取 4 字节消息体长度(MessagePacketLength),校验其是否超出最大限制(Packet.PacketBodyMaxLength)或数据不完整(count < MessagePacketLength),异常时抛出 ScanException;从 4 字节偏移(Packet.PacketLength)读取 4 字节协议编号(ProtocolCode),包含消息分类、序列化协议、具体索引的三元复合标识;从 8 字节偏移(Packet.InnerPacketRpcIdLocation)读取 4 字节 RpcId(远程调用标识),用于请求 - 响应配对;从 12 字节偏移(Packet.InnerPacketRouteRouteIdLocation)读取 8 字节 RouteId(路由标识),支撑内网分布式节点间的消息路由。

解析结果封装 :创建 InnerPackInfo 作为解析结果载体,赋值 RpcIdRouteIdProtocolCode 等元数据;从内存池租赁 MemoryStreamBuffer,将缓冲区数据写入流中,完成从原始字节到可处理消息载体的转换,为后续反序列化和路由分发提供基础。

打包流程(Pack):从业务消息到字节流的编码

方法重载逻辑 :重写的Pack方法根据输入参数是否包含MemoryStreamBuffer(内存流),自动路由到不同处理逻辑 ------ 若内存流已存在(复用场景),调用Pack(ref rpcId, ref routeId, memoryStream);若需新建,则调用Pack(ref rpcId, ref routeId, message)从业务消息(IMessage)开始编码。

内存流复用场景:高效更新元数据

核心内存操作 :针对已有内存流的情况,通过fixed关键字固定流缓冲区地址,使用指针直接写入RpcId(8 字节偏移)和RouteId(12 字节偏移),无需重新序列化消息体,仅更新元数据,提升复用效率(标注[MethodImpl(MethodImplOptions.AggressiveInlining)]强制内联,减少方法调用开销)。

新建内存流场景:完整数据包构建

初始化与序列化 :从网络内存池(Network.MemoryStreamBufferPool)租赁内存流(MemoryStreamBuffer),通过 memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin) 操作定位读写指针 ------ 其中 Packet.InnerPacketHeadLength 是框架定义的内网消息头部固定长度(通常为 20 字节),用于标识头部区域的总大小;SeekOrigin.Begin 则指定 "偏移量的计算起点为流的起始位置(0 字节处)"。两者结合的作用是将指针移动到 "头部结束的位置"(即 20 字节偏移处),从而在内存流起始的 0-19 字节预留出头部空间,避免后续写入业务数据时覆盖头部元信息。基于消息自带的 OpCodeIdStructOpCodeProtocolType(序列化协议类型),通过 SerializerManager 获取对应的序列化器(如 ProtoBuf 或 Bson 序列化器),将业务消息(IMessage)序列化到内存流中(从 20 字节位置开始写入);序列化完成后,通过 memoryStream.Position 记录当前指针位置,即 "头部长度 + 消息体长度" 的总长度(例如头部 20 字节 + 消息体 100 字节,总长度为 120 字节)。

长度计算与校验 :计算消息体实际长度(packetBodyCount = 总长度 - Packet.InnerPacketHeadLength,如 120-20=100)。若 packetBodyCount 为 0,需特殊处理为 -1------ 这是针对 protobuf 的优化,当消息对象字段全为默认值时,序列化后无内容,此处用-1明确标识 "空消息",以区分 "数据不完整"(后者表现为接收长度小于预期);若 packetBodyCount 超过 Packet.PacketBodyMaxLength(最大消息体长度限制),则抛出异常,防止超大数据包占用过多传输资源,保障内网通信稳定性。

头部信息写入 :在预留的 0-19 字节头部空间内,按固定偏移写入关键元数据 ------ 0-3 字节写入 packetBodyCount(消息体长度),供接收方验证完整性;4-7 字节写入 opCode(协议编号,即 OpCodeIdStruct 转换的 32 位整数,包含消息分类、序列化协议等标识);8-11 字节写入 RpcId(远程调用标识,用于请求 - 响应配对);12-19 字节写入 RouteId(路由标识,指定内网节点路由目标)。这些指针操作直接访问内存地址,规避托管代码的边界检查与类型转换开销,高效完成头部填充,最终形成 "头部(20 字节)+ 消息体(N 字节)" 的完整数据包。

OuterWebglBufferPacketParser 功能解析:WebGL 环境下的客户端 - 服务器消息编解码全链路

外网通信(客户端 - 服务器)的解包逻辑由 OuterBufferPacketParser 实现,其核心逻辑与内网解析器类似,但针对外网场景简化了路由信息(Outer 数据包的 RouteId 通常为空)。

特别地,框架为 WebGL 环境提供了 OuterWebglBufferPacketParser。由于 WebGL 环境中 JavaScriptC# 交互存在限制,内存管理方式与原生环境不同,该解析器通过调整内存流的读写逻辑适配 WebGL 场景,具体实现如下:

解包流程(UnPack):从字节流到消息载体的转换

参数校验 :验证缓冲区长度(byte[])与待解析数据长度(count)的有效性,确保头部信息完整 ------ 若缓冲区长度不足 count 或数据总长度小于外网消息头固定长度(Packet.OuterPacketHeadLength),则抛出异常或判定为数据不完整,终止解析。

缓冲区的核心作用与 WebGL 适配逻辑 :WebGL 环境优先使用字节缓冲区(byte[])而非内存流(MemoryStream),核心原因在于:

  1. 适配底层数据形式WebGL 底层网络接口(如 WebSocket)传递的原始数据为连续字节数组,缓冲区作为 "原生容器" 可直接承接数据,避免内存流封装带来的转换开销;
  2. 优化跨语言交互 :JavaScript 与 C# 在 WebGL 中对基础数组类型(byte[])的交互支持更直接,而内存流的底层缓冲区需额外提取才能被 JS 识别,缓冲区作为中间载体可减少跨语言转换复杂度;
  3. 满足轻量操作需求:解包仅需读取固定偏移的元数据(长度、协议号等),无需内存流的指针管理、动态扩容等复杂功能,缓冲区的直接索引访问更高效。

头部信息提取WebGL 禁用 unsafe 非托管指针操作,BitConverter 作为托管转换工具,可安全地将缓冲区中连续字节转换为 in(in,关键字,只读引用)/uint 等类型(如从 buffer[0..3] 读取 MessagePacketLength),既规避浏览器沙箱对内存越界的拦截,又兼容托管内存的自动垃圾回收机制。

通过 BitConverter 从固定偏移读取关键信息 ------ 0 偏移读取 MessagePacketLength(消息体长度)并校验有效性,Packet.PacketLength 偏移读取 ProtocolCode(协议编号),Packet.OuterPacketRpcIdLocation 偏移读取 RpcId(远程调用标识);外网通信无需分布式路由,故无 RouteId 相关处理。

解析结果封装 :创建 OuterPackInfo 载体并赋值元数据,从内存池租赁 MemoryStreamBuffer,将缓冲区数据写入流中,完成从原始字节到可处理消息载体的转换,适配后续序列化框架的流接口需求。

打包流程(Pack):从业务消息到字节流的编码

方法重载逻辑 :根据输入参数是否包含 MemoryStreamBuffer(内存流)自动路由处理 ------ 复用内存流时调用 Pack(ref rpcId, memoryStream),新建时调用 Pack(ref rpcId, message);外网无分布式路由需求,故无需 routeId 参数。

内存流复用场景:高效更新元数据

核心内存操作var buffer = memoryStream.GetBuffer().AsSpan(); 是 WebGL 环境下高效操作的关键,memoryStream.GetBuffer() -- 获取内存流底层字节数组(byte[]),不复制数据,直接引用原始存储容器;.AsSpan() -- 转换为 Span<byte>(连续内存的类型安全视图),实现零复制访问指定区间内存,自带边界检查(避免 WebGL 内存越界异常),可通过 Slice() 精准定位头部元数据写入位置(如 RpcId 偏移区间)。

MemoryMarshal.Write 是 WebGL 禁用指针后的高效替代工具,可直接将 rpcId 写入 Span<byte> 的指定偏移(Packet.OuterPacketRpcIdLocation),无需先转换为字节数组再复制,实现 "值类型→内存块" 的直接映射,兼顾安全性与效率。

标注 [MethodImpl(MethodImplOptions.AggressiveInlining)] 强制内联,减少方法调用开销。

新建内存流场景:完整数据包构建

初始化与序列化 :从网络内存池租赁 MemoryStreamBuffer,通过 memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin) 预留头部空间;基于消息的 OpCodeProtocolType 获取序列化器,将消息序列化至流中(从头部结束位置开始写入),通过 memoryStream.Position 记录总长度。

长度计算与校验 :计算消息体长度(packetBodyCount = 总长度 - Packet.OuterPacketHeadLength),若为 0 则特殊处理为 -1(区分空消息与数据不完整),若超出 Packet.PacketBodyMaxLength 则抛出异常。

头部信息写入 :通过获取 Span<byte> 视图,利用 MemoryMarshal.Write 按固定偏移写入元数据 ------ 0-3 字节写入 packetBodyCountPacket.PacketLength 偏移写入 opCodePacket.OuterPacketRpcIdLocation 偏移写入 RpcId;借助 Span<byte> 的区间定位能力精准操作,避免额外内存分配,最终形成 "头部(Packet.OuterPacketHeadLength 字节)+ 消息体(N 字节)" 的完整数据包,可直接作为缓冲区发送。

ReadOnlyMemoryPacketParser:只读内存块的零拷贝解析

ReadOnlyMemoryPacketParser 针对 ReadOnlyMemory<byte> 设计,适用于需要零拷贝(避免字节数组复制)的场景(如 TCP 协议)。其源码位于 Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs

csharp 复制代码
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Fantasy.Helper;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.PacketParser.Interface;
using Fantasy.Serialize;

// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.

namespace Fantasy.PacketParser
{
    internal abstract class ReadOnlyMemoryPacketParser : APacketParser
    {
        /// <summary>
        /// 一个网络消息包
        /// </summary>
        protected APackInfo PackInfo;

        protected int Offset;
        protected int MessageHeadOffset;
        protected int MessageBodyOffset;
        protected int MessagePacketLength;
        protected bool IsUnPackHead = true;
        protected readonly byte[] MessageHead = new byte[20];
        public ReadOnlyMemoryPacketParser() { }

        public abstract bool UnPack(ref ReadOnlyMemory<byte> buffer, out APackInfo packInfo);

        public override void Dispose()
        {
            Offset = 0;
            MessageHeadOffset = 0;
            MessageBodyOffset = 0;
            MessagePacketLength = 0;
            IsUnPackHead = true;
            PackInfo = null;
            Array.Clear(MessageHead, 0, 20);
            base.Dispose();
        }
    }

#if FANTASY_NET
    internal sealed class InnerReadOnlyMemoryPacketParser : ReadOnlyMemoryPacketParser
    {
        public override unsafe bool UnPack(ref ReadOnlyMemory<byte> buffer, out APackInfo packInfo)
        {
            packInfo = null;
            var readOnlySpan = buffer.Span;
            var bufferLength = buffer.Length - Offset;
            
            if (bufferLength == 0)
            {
                // 没有剩余的数据需要处理、等待下一个包再处理。
                Offset = 0;
                return false;
            }
            
            if (IsUnPackHead)
            {
                fixed (byte* bufferPtr = readOnlySpan)
                fixed (byte* messagePtr = MessageHead)
                {
                    // 在当前buffer中拿到包头的数据
                    var innerPacketHeadLength = Packet.InnerPacketHeadLength - MessageHeadOffset;
                    var copyLength = Math.Min(bufferLength, innerPacketHeadLength);
                    Buffer.MemoryCopy(bufferPtr + Offset, messagePtr + MessageHeadOffset, innerPacketHeadLength, copyLength);
                    Offset += copyLength;
                    MessageHeadOffset += copyLength;
                    // 检查是否有完整包头
                    if (MessageHeadOffset == Packet.InnerPacketHeadLength)
                    {
                        // 通过指针直接读取协议编号、messagePacketLength protocolCode rpcId routeId
                        MessagePacketLength = *(int*)messagePtr;
                        // 检查消息体长度是否超出限制
                        if (MessagePacketLength > Packet.PacketBodyMaxLength)
                        {
                            throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
                        }

                        PackInfo = InnerPackInfo.Create(Network);
                        var memoryStream = PackInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, Packet.InnerPacketHeadLength + MessagePacketLength);
                        PackInfo.RpcId = *(uint*)(messagePtr + Packet.InnerPacketRpcIdLocation);
                        PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength);
                        PackInfo.RouteId = *(long*)(messagePtr + Packet.InnerPacketRouteRouteIdLocation);
                        memoryStream.Write(MessageHead);
                        IsUnPackHead = false;
                        bufferLength -= copyLength;
                        MessageHeadOffset = 0;
                    }
                    else
                    {
                        Offset = 0;
                        return false;
                    }
                }
            }

            if (MessagePacketLength == -1)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packInfo = PackInfo;
                PackInfo = null;
                IsUnPackHead = true;
                return true;
            }

            if (bufferLength == 0)
            {
                // 没有剩余的数据需要处理、等待下一个包再处理。
                Offset = 0;
                return false;
            }

            // 处理包消息体
            var innerPacketBodyLength = MessagePacketLength - MessageBodyOffset;
            var copyBodyLength = Math.Min(bufferLength, innerPacketBodyLength);
            // 写入数据到消息体中
            PackInfo.MemoryStream.Write(readOnlySpan.Slice(Offset, copyBodyLength));
            Offset += copyBodyLength;
            MessageBodyOffset += copyBodyLength;
            // 检查是否是完整的消息体
            if (MessageBodyOffset == MessagePacketLength)
            {
                packInfo = PackInfo;
                PackInfo = null;
                IsUnPackHead = true;
                MessageBodyOffset = 0;
                return true;
            }
            Offset = 0;
            return false;
        }

        public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
        {
            return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream)
        {
            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
                *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
            }
            
            return memoryStream;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message)
        {
            var memoryStreamLength = 0;
            var messageType = message.GetType();
            var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
            OpCodeIdStruct opCodeIdStruct = message.OpCode();
            memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);

            if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                serializer.Serialize(messageType, message, memoryStream);
                memoryStreamLength = (int)memoryStream.Position;
            }
            else
            {
                Log.Error($"type:{messageType} Does not support processing protocol");
            }

            var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
            var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength;
            
            if (packetBodyCount == 0)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                // 其实可以不用设置-1、解包的时候判断如果是0也可以、但我仔细想了下,还是用-1代表更加清晰。
                packetBodyCount = -1;
            }
            
            if (packetBodyCount > Packet.PacketBodyMaxLength)
            {
                // 检查消息体长度是否超出限制
                throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
            }
            
            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(int*)bufferPtr = packetBodyCount;
                *(uint*)(bufferPtr + Packet.PacketLength) = opCode;
                *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
                *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
            }
            
            return memoryStream;
        }
    }
#endif
    internal sealed class OuterReadOnlyMemoryPacketParser : ReadOnlyMemoryPacketParser
    {
        public override unsafe bool UnPack(ref ReadOnlyMemory<byte> buffer, out APackInfo packInfo)
        {
            packInfo = null;
            var readOnlySpan = buffer.Span;
            var bufferLength = buffer.Length - Offset;
            
            if (bufferLength == 0)
            {
                // 没有剩余的数据需要处理、等待下一个包再处理。
                Offset = 0;
                return false;
            }
            
            if (IsUnPackHead)
            {
                fixed (byte* bufferPtr = readOnlySpan)
                fixed (byte* messagePtr = MessageHead)
                {
                    // 在当前buffer中拿到包头的数据
                    var outerPacketHeadLength = Packet.OuterPacketHeadLength - MessageHeadOffset;
                    var copyLength = Math.Min(bufferLength, outerPacketHeadLength);
                    Buffer.MemoryCopy(bufferPtr + Offset, messagePtr + MessageHeadOffset, outerPacketHeadLength, copyLength);
                    Offset += copyLength;
                    MessageHeadOffset += copyLength;
                    // 检查是否有完整包头
                    if (MessageHeadOffset == Packet.OuterPacketHeadLength)
                    {
                        // 通过指针直接读取协议编号、messagePacketLength protocolCode rpcId routeId
                        MessagePacketLength = *(int*)messagePtr;
                        // 检查消息体长度是否超出限制
                        if (MessagePacketLength > Packet.PacketBodyMaxLength)
                        {
                            throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
                        }

                        PackInfo = OuterPackInfo.Create(Network);
                        PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength);
                        PackInfo.RpcId = *(uint*)(messagePtr + Packet.OuterPacketRpcIdLocation);
                        var memoryStream = PackInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, Packet.OuterPacketHeadLength + MessagePacketLength);
                        memoryStream.Write(MessageHead);
                        IsUnPackHead = false;
                        bufferLength -= copyLength;
                        MessageHeadOffset = 0;
                    }
                    else
                    {
                        Offset = 0;
                        return false;
                    }
                }
            }
            
            if (MessagePacketLength == -1)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packInfo = PackInfo;
                PackInfo = null;
                IsUnPackHead = true;
                return true;
            }

            if (bufferLength == 0)
            {
                // 没有剩余的数据需要处理、等待下一个包再处理。
                Offset = 0;
                return false;
            }
            // 处理包消息体
            var outerPacketBodyLength = MessagePacketLength - MessageBodyOffset;
            var copyBodyLength = Math.Min(bufferLength, outerPacketBodyLength);
            // 写入数据到消息体中
            PackInfo.MemoryStream.Write(readOnlySpan.Slice(Offset, copyBodyLength));
            Offset += copyBodyLength;
            MessageBodyOffset += copyBodyLength;
            // 检查是否是完整的消息体
            if (MessageBodyOffset == MessagePacketLength)
            {
                packInfo = PackInfo;
                PackInfo = null;
                IsUnPackHead = true;
                MessageBodyOffset = 0;
                return true;
            }

            Offset = 0;
            return false;
        }

        public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
        {
            return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream)
        {
            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
            }
            
            return memoryStream;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message)
        {
            var memoryStreamLength = 0;
            var messageType = message.GetType();
            var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
            OpCodeIdStruct opCodeIdStruct = message.OpCode();
            memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
            
            if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                serializer.Serialize(messageType, message, memoryStream);
                memoryStreamLength = (int)memoryStream.Position;
            }
            else
            {
                Log.Error($"type:{messageType} Does not support processing protocol");
            }
            
            var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
            var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;

            if (packetBodyCount == 0)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packetBodyCount = -1;
            }
            
            if (packetBodyCount > Packet.PacketBodyMaxLength)
            {
                // 检查消息体长度是否超出限制
                throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
            }
            
            fixed (byte* bufferPtr = memoryStream.GetBuffer())
            {
                *(int*)bufferPtr = packetBodyCount;
                *(uint*)(bufferPtr + Packet.PacketLength) = opCode;
                *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
            }
            
            return memoryStream;
        }
    }
}
ReadOnlyMemoryPacketParser 的基础构成:基类继承关系与核心成员定义

ReadOnlyMemoryPacketParser 继承自抽象基类 APacketParser,延续了数据包解析器的基础契约。它重写的 Dispose 方法在释放基类资源的同时,额外清理了自身维护的状态变量(OffsetMessageHeadOffset 等)及缓冲区,形成完整的生命周期管理链路。

ReadOnlyMemoryPacketParser 类内定义了关键状态变量:PackInfo(解析结果载体)、Offset(当前解析偏移)、MessageHeadOffset(包头解析偏移)、MessageBodyOffset(包体解析偏移)、MessagePacketLength(消息包长度)、IsUnPackHead(包头解析状态标记)、MessageHead(20 字节包头缓冲区),用于暂存解析过程中的核心元数据;抽象方法 UnPack 则定义了基于 ReadOnlyMemory<byte> 的解析入口契约,要求子类实现具体逻辑(输入只读内存缓冲区,输出解析结果载体 APackInfo)。

BufferPacketParser 相比,ReadOnlyMemoryPacketParser 类增加了更多关键状态变量(如上述的 OffsetMessageHeadOffset),主要用于处理 TCP 协议的分包粘包场景 ------ 通过维护解析偏移量,支持对不完整数据包的渐进式解析。

InnerReadOnlyMemoryPacketParser 功能解析:内网 TCP 协议的编解码全链路

InnerReadOnlyMemoryPacketParser 是针对内网 TCP 协议设计的高性能编解码实现,基于 ReadOnlyMemory<byte> 实现零复制内存操作,通过指针直接访问内存提升解析效率,完整覆盖服务器间通信的 "字节流→消息对象" 与 "消息对象→字节流" 全链路。

解包流程(UnPack):从只读内存到消息载体的转换

核心目标:处理 TCP 粘包 / 分包问题,从接收到的只读内存缓冲区中逐步拼接并解析出完整数据包,提取元数据(包头信息)和消息体原始字节,最终封装为可被业务层处理的消息载体。

参数校验 :先计算输入缓冲区(ReadOnlyMemory<byte>)的剩余可读取长度(buffer.Length - Offset),以此判断数据是否可处理:若剩余长度为 0,说明当前无有效数据,重置 Offset 并等待后续数据分片;若处于包头解析阶段(IsUnPackHead = true),则检查剩余长度是否满足 "包头未读取部分的需求"(Packet.InnerPacketHeadLength - MessageHeadOffset),不满足则终止解析(避免无效内存访问),等待下一次数据补充。

unsafe 代码与零复制优化 :通过 fixed 关键字固定缓冲区(readOnlySpan)和本地包头缓冲区(MessageHead)的内存地址,转换为 byte* 指针直接操作内存 ------ 其中 readOnlySpanbuffer.Span 生成的 ReadOnlySpan<byte> 视图,实现对原始内存的零复制访问(无需复制数据即可操作)。

借助指针操作规避托管代码的边界检查开销,结合 Buffer.MemoryCopy 从输入缓冲区拷贝数据到 MessageHead:拷贝长度取 "缓冲区剩余可读取长度" 与 "包头未读取长度" 的最小值(Math.Min(bufferLength, innerPacketHeadLength)),既避免内存越界(不超过缓冲区实际有效数据范围),又适配 TCP 分包特性(有多少数据先拷贝多少,剩余部分后续补充)。

头部信息提取 :当包头数据读取完整(MessageHeadOffset == Packet.InnerPacketHeadLength),通过指针从 MessageHead 的固定偏移精准提取关键元数据:

  • 0 偏移MessagePacketLength = *(int*)messagePtr,读取 4 字节消息体总长度,校验其是否超出 Packet.PacketBodyMaxLength 限制(超限则抛出 ScanException,防止超大包攻击);
  • Packet.PacketLength 偏移(4 字节处)PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength),读取 4 字节协议编号(标识消息类型与序列化协议);
  • Packet.InnerPacketRpcIdLocation 偏移(8 字节处)PackInfo.RpcId = *(uint*)(messagePtr + Packet.InnerPacketRpcIdLocation),读取 4 字节远程调用标识(用于请求 - 响应配对);
  • Packet.InnerPacketRouteRouteIdLocation 偏移(12 字节处)PackInfo.RouteId = *(long*)(messagePtr + Packet.InnerPacketRouteRouteIdLocation),读取 8 字节路由标识(支撑内网分布式节点路由)。

包体解析与组装:头部解析完成后,进入包体处理阶段,同样遵循 "分阶段累加" 逻辑:

  • 空消息特殊处理 :若 MessagePacketLength == -1(标识 Protobuf 空消息,即消息字段全为默认值,无实际字节),直接将 PackInfo 作为完整结果,重置状态(PackInfo = nullIsUnPackHead = true)并返回成功;
  • 缓冲区数据检查 :若缓冲区剩余长度为 0,说明当前数据不足,重置 Offset 并返回失败,等待后续数据;
  • 包体数据拷贝 :计算待读取的包体剩余长度(innerPacketBodyLength = MessagePacketLength - MessageBodyOffset),拷贝长度取 "缓冲区剩余长度" 与 "待读取长度" 的最小值(copyBodyLength = Math.Min(bufferLength, innerPacketBodyLength)),通过 readOnlySpan.Slice(Offset, copyBodyLength) 截取有效字节,写入 PackInfo.MemoryStream(消息体暂存容器),同步更新 Offset(缓冲区已处理偏移)和 MessageBodyOffset(已累加的包体长度);
  • 完整性校验 :当 MessageBodyOffset == MessagePacketLength 时,包体组装完整,将 PackInfo 作为结果,重置状态(PackInfo = nullIsUnPackHead = trueMessageBodyOffset = 0)并返回成功;否则,重置 Offset 等待后续数据继续拼接。

解析结果封装 :通过创建 InnerPackInfo 作为载体,从内存池租赁 MemoryStreamBuffer(预分配 "包头 + 包体" 总长度空间),先写入完整包头(MessageHead),再逐步累加包体原始字节至内存流,最终形成包含完整元数据(RpcIdRouteId 等)和消息体内存流的解析结果。该载体为后续序列化框架(如 Protobuf)从内存流反序列化为 IMessage 对象提供输入,完成从 "只读内存字节流" 到 "业务可处理消息" 的全链路转换。

打包流程(Pack):从业务消息到字节流的编码

核心目标 :将业务消息(IMessage)按内网协议格式编码为字节流,添加包头元数据(长度、协议号等),生成符合 TCP 传输要求的完整数据包,适配内网高频通信场景。

方法重载逻辑 :重写的 Pack 方法根据输入参数自动路由处理逻辑,兼顾 "新建数据包" 与 "复用已有内存流" 两种场景:

  • 若输入已存在 MemoryStreamBuffer(内存流),调用 Pack(ref rpcId, ref routeId, memoryStream) 仅更新元数据(无需重新序列化消息体),提升复用效率;
  • 若需新建数据包,调用 Pack(ref rpcId, ref routeId, message) 从业务消息对象开始完整编码,生成包含头部和消息体的完整字节流。
内存流复用场景:高效更新元数据

核心内存操作:针对已有内存流的复用场景,通过底层指针操作快速更新关键元数据,避免重复序列化:

  • fixed 关键字固定内存流底层缓冲区的地址(bufferPtr),转换为 byte* 指针直接访问内存;
  • 按固定偏移写入元数据,在 Packet.InnerPacketRpcIdLocation(8 字节处)写入 RpcId*(uint*)(bufferPtr + ...) = rpcId),在 Packet.InnerPacketRouteRouteIdLocation(12 字节处)写入 RouteId*(long*)(bufferPtr + ...) = routeId);
  • 标注 [MethodImpl(MethodImplOptions.AggressiveInlining)] 强制编译器内联,减少方法调用开销,进一步提升高频场景下的性能。
新建内存流场景:完整数据包构建

初始化与序列化 :从网络内存池(Network.MemoryStreamBufferPool)租赁 MemoryStreamBuffer,通过 memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin) 操作,在流的起始位置预留 20 字节空间(用于存储包头);基于消息的 OpCodeProtocolType(序列化协议类型)从 SerializerManager 获取对应序列化器(如 Protobuf),将业务消息(IMessage)序列化至流中(从 20 字节位置开始写入,避免覆盖预留的头部空间)。

长度计算与校验 :计算消息体实际长度:packetBodyCount = 内存流总长度 - Packet.InnerPacketHeadLength(总长度即 memoryStream.Position,包含头部预留空间 + 消息体);若 packetBodyCount 为 0 便是需特殊处理的空消息(Protobuf 对全默认值对象的序列化优化,无实际字节),则设为 -1(标识 Protobuf 空消息)以区分 "数据不完整";若 packetBodyCount 超过 Packet.PacketBodyMaxLength(最大包体限制),抛出异常防止超大包占用传输资源。

头部信息写入 :用 fixed 关键字固定内存流缓冲区地址,通过指针在预留的 20 字节头部按固定偏移写入元数据:

  • 0-3 字节:*(int*)bufferPtr = packetBodyCount(消息体长度,供接收方校验完整性);
  • 4-7 字节:*(uint*)(bufferPtr + Packet.PacketLength) = opCode(协议编号,标识消息类型);
  • 8-11 字节:*(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId(远程调用标识);
  • 12-19 字节:*(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId(内网路由标识)。

数据包构建流程:从业务消息开始构建完整数据包,流程分为 "初始化→序列化→长度校验→头部写入" 四步,最终形成 "头部(20 字节)+ 消息体(N 字节)" 的完整数据包,通过指针直接操作内存实现高效编码,适配内网 TCP 高频通信场景需求。

CircularBufferPacketParser:循环缓冲区的遗留实现

CircularBufferPacketParser 是框架遗留的循环缓冲区解析器,源码注释提到 "处理分包和粘包逻辑不完整,目前未使用,留作备份"。在此便不做阐述了。

工厂模式与多协议适配:解析器的动态调度

为了屏蔽不同解析器的实现细节,使上层逻辑能根据网络类型和协议动态选择解析器,Fantasy 框架通过 PacketParserFactory 实现了解析器的工厂模式管理。结合 SerializerManagerISerialize 的管理,形成了完整的多协议适配体系,确保 AMessage 实例在各种场景下的正确序列化与反序列化。

PacketParserFactory:解析器的 "生产车间"

核心职责是根据网络目标(Inner/Outer)和解析器类型创建对应的实例,以适配不同的网络环境和 AMessage 传输需求。其源码位于 Runtime/Core/Network/Message/PacketParser/PacketParserFactory.cs

ini 复制代码
using System;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.PacketParser.Interface;

// ReSharper disable PossibleNullReferenceException
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8603 // Possible null reference return.
namespace Fantasy.PacketParser
{
    internal static class PacketParserFactory
    {
#if FANTASY_NET
        internal static ReadOnlyMemoryPacketParser CreateServerReadOnlyMemoryPacket(ANetwork network)
        {
            ReadOnlyMemoryPacketParser readOnlyMemoryPacketParser = null;
            
            switch (network.NetworkTarget)
            {
                case NetworkTarget.Inner:
                {
                    readOnlyMemoryPacketParser = new InnerReadOnlyMemoryPacketParser();
                    break;
                }
                case NetworkTarget.Outer:
                {
                    readOnlyMemoryPacketParser = new OuterReadOnlyMemoryPacketParser();
                    break;
                }
            }
            
            readOnlyMemoryPacketParser.Scene = network.Scene;
            readOnlyMemoryPacketParser.Network = network;
            readOnlyMemoryPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
            return readOnlyMemoryPacketParser;
        }

        public static BufferPacketParser CreateServerBufferPacket(ANetwork network)
        {
            BufferPacketParser bufferPacketParser = null;
            
            switch (network.NetworkTarget)
            {
                case NetworkTarget.Inner:
                {
                    bufferPacketParser = new InnerBufferPacketParser();
                    break;
                }
                case NetworkTarget.Outer:
                {
                    bufferPacketParser = new OuterBufferPacketParser();
                    break;
                }
            }
            
            bufferPacketParser.Scene = network.Scene;
            bufferPacketParser.Network = network;
            bufferPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
            return bufferPacketParser;
        }
#endif
        internal static ReadOnlyMemoryPacketParser CreateClientReadOnlyMemoryPacket(ANetwork network)
        {
            ReadOnlyMemoryPacketParser readOnlyMemoryPacketParser = null;

            switch (network.NetworkTarget)
            {
#if FANTASY_NET
                case NetworkTarget.Inner:
                {
                    readOnlyMemoryPacketParser = new InnerReadOnlyMemoryPacketParser();
                    break;
                }
#endif
                case NetworkTarget.Outer:
                {
                    readOnlyMemoryPacketParser = new OuterReadOnlyMemoryPacketParser();
                    break;
                }
            }

            readOnlyMemoryPacketParser.Scene = network.Scene;
            readOnlyMemoryPacketParser.Network = network;
            readOnlyMemoryPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
            return readOnlyMemoryPacketParser;
        }

#if !FANTASY_WEBGL
        public static BufferPacketParser CreateClientBufferPacket(ANetwork network)
        {
            BufferPacketParser bufferPacketParser = null;

            switch (network.NetworkTarget)
            {
#if FANTASY_NET
                case NetworkTarget.Inner:
                {
                    bufferPacketParser = new InnerBufferPacketParser();
                    break;
                }
#endif
                case NetworkTarget.Outer:
                {
                    bufferPacketParser = new OuterBufferPacketParser();
                    break;
                }
            }

            bufferPacketParser.Scene = network.Scene;
            bufferPacketParser.Network = network;
            bufferPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
            return bufferPacketParser;
        }
#endif
        public static T CreateClient<T>(ANetwork network) where T : APacketParser
        {
            var packetParserType = typeof(T);
            
            switch (network.NetworkTarget)
            {
#if FANTASY_NET
                case NetworkTarget.Inner:
                {
                    APacketParser innerPacketParser = null;

                    if (packetParserType == typeof(ReadOnlyMemoryPacketParser))
                    {
                        innerPacketParser = new InnerReadOnlyMemoryPacketParser();
                    }
                    else if (packetParserType == typeof(BufferPacketParser))
                    {
                        innerPacketParser = new InnerBufferPacketParser();
                    }
                    // else if(packetParserType == typeof(CircularBufferPacketParser))
                    // {
                    //     innerPacketParser = new InnerCircularBufferPacketParser();
                    // }

                    innerPacketParser.Scene = network.Scene;
                    innerPacketParser.Network = network;
                    innerPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
                    return (T)innerPacketParser;
                }
#endif
                case NetworkTarget.Outer:
                {
                    APacketParser outerPacketParser = null;

                    if (packetParserType == typeof(ReadOnlyMemoryPacketParser))
                    {
                        outerPacketParser = new OuterReadOnlyMemoryPacketParser();
                    }
                    else if (packetParserType == typeof(BufferPacketParser))
                    {
#if FANTASY_WEBGL
                        outerPacketParser = new OuterWebglBufferPacketParser();
#else
                        outerPacketParser = new OuterBufferPacketParser();
#endif
                    }
                    outerPacketParser.Scene = network.Scene;
                    outerPacketParser.Network = network;
                    outerPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
                    return (T)outerPacketParser;
                }
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}
PacketParserFactory 的核心功能与实现逻辑:数据包解析器的工厂管理

服务器端解析器的针对性创建机制 :通过 CreateServerReadOnlyMemoryPacketCreateServerBufferPacket 方法,为服务器端创建适配的数据包解析器。根据 network.NetworkTarget 区分内网(NetworkTarget.Inner)与外网(NetworkTarget.Outer)场景,分别返回 InnerReadOnlyMemoryPacketParser/InnerBufferPacketParserOuterReadOnlyMemoryPacketParser/OuterBufferPacketParser。创建后统一配置 SceneNetworkMessageDispatcherComponent,确保解析器关联必要的运行时环境,适配服务器端内网 / 外网通信的解析需求。

客户端解析器的场景化创建机制 :提供 CreateClientReadOnlyMemoryPacketCreateClientBufferPacket 方法,为客户端创建解析器。逻辑与服务器端类似,根据 network.NetworkTarget 选择内网 / 外网解析器(内网解析器受 #if FANTASY_NET 条件编译限制),其中 CreateClientBufferPacket 仅在非 WebGL 环境生效(#if !FANTASY_WEBGL)。创建后同样配置基础属性,满足客户端不同网络场景的解析需求。

泛型驱动的客户端解析器创建CreateClient<T> 方法支持按指定解析器类型(T 继承 APacketParser)动态创建实例。根据网络目标分支处理:内网场景下,若 TReadOnlyMemoryPacketParser 则返回 InnerReadOnlyMemoryPacketParser,为 BufferPacketParser 则返回 InnerBufferPacketParser;外网场景下,根据 T 类型及是否为 WebGL 环境,返回 OuterReadOnlyMemoryPacketParserOuterBufferPacketParserOuterWebglBufferPacketParser。创建后配置属性并强转为 T 类型,兼顾类型安全与场景适配。

条件编译的环境适配 :通过 #if FANTASY_NET 限制内网解析器仅在支持网络模块的环境生效,通过 #if !FANTASY_WEBGL 区分 WebGL 与非 WebGL 环境的缓冲区解析器,确保解析器创建逻辑与运行环境匹配,避免不兼容组件在特定环境中实例化。

多协议适配的完整链路(解析器 + 序列化器 + AMessage)

框架通过 "解析器类型 + 网络目标 + 协议类型" 的三维度组合实现多协议适配,其中 ISerialize 是连接解析器与协议格式的核心纽带,AMessage 是最终的业务数据载体: 解析器类型BufferPacketParser 适配 KCPReadOnlyMemoryPacketParser 适配 TCP/WebSocket,负责字节流的拆分与重组,最终生成包含 AMessage 字节数据的 APackInfo

网络目标Inner 解析器处理服务器间通信(包含 RouteId 等路由信息),Outer 解析器处理客户端 - 服务器通信(简化路由),通过 PacketParserFactory 动态创建,确保 AMessage 在不同网络场景下的正确传输。

协议类型 :通过 OpCodeProtocolType 区分 ProtoBuf/Bson 等序列化协议,SerializerManager 管理对应的 ISerialize 实现类(ProtoBufPackHelperNet/ProtoBufPackHelperUnity/BsonSerialize),解析器在打包 / 解包时动态调用,完成 AMessage 与字节流的转换。

数据包载体的具体实现:从解析到业务的桥梁

APackInfo 的子类(InnerPackInfoOuterPackInfoProcessPackInfo)是解析结果的具体载体,负责内存管理、反序列化等核心逻辑,是连接解析器与业务逻辑的桥梁,其 Deserialize 方法最终生成 AMessage 实例。

InnerPackInfo:服务器内部通信的数据包封装

用于服务器集群内部节点间的通信(如网关服与逻辑服、逻辑服与数据服),封装内网数据包信息,侧重高效传输与路由能力。源码位于 Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs

csharp 复制代码
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract

using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.PacketParser.Interface;
using Fantasy.Pool;
using Fantasy.Serialize;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8603 // Possible null reference return.
#if FANTASY_NET
namespace Fantasy.PacketParser
{
    public sealed class InnerPackInfo : APackInfo
    {
        private readonly Dictionary<Type, Func<object>> _createInstances = new Dictionary<Type, Func<object>>();

        public override void Dispose()
        {
            if (IsDisposed)
            {
                return;
            }

            var network = Network;
            base.Dispose();
            network.ReturnInnerPackInfo(this);
        }

        public static InnerPackInfo Create(ANetwork network)
        {
            var innerPackInfo = network.RentInnerPackInfo();
            innerPackInfo.Network = network;
            innerPackInfo.IsDisposed = false;
            return innerPackInfo;
        }

        public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
        {
            return MemoryStream ??= Network.MemoryStreamBufferPool.RentMemoryStream(memoryStreamBufferSource, size);
        }

        public override object Deserialize(Type messageType)
        {
            if (MemoryStream == null)
            {
                Log.Debug("Deserialize MemoryStream is null");
                return null;
            }

            MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);

            if (MemoryStream.Length == 0)
            {
                if (_createInstances.TryGetValue(messageType, out var createInstance))
                {
                    return createInstance();
                }

                createInstance = CreateInstance.CreateObject(messageType);
                _createInstances.Add(messageType, createInstance);
                return createInstance();
            }

            if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                var obj = serializer.Deserialize(messageType, MemoryStream);
                MemoryStream.Seek(0, SeekOrigin.Begin);
                return obj;
            }
            
            MemoryStream.Seek(0, SeekOrigin.Begin);
            Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
            return null;
        }
    }
}
#endif
InnerPackInfo 的核心功能与实现逻辑:内网数据包的载体与资源管理

对象池化的资源循环机制 :重写 Dispose 方法实现实例与资源的双重回收。首先通过 IsDisposed 状态避免重复释放;调用基类 Dispose 清理元数据(RpcIdRouteId 等)并归还 MemoryStream 至内存池;最终通过 network.ReturnInnerPackInfo(this) 将自身返还至网络实例的对象池,结合 "对象池租赁 - 归还" 模式,避免频繁 new 操作导致的 GC 压力,适配内网服务器间高并发通信的性能需求。

基于对象池的高效实例创建机制 :通过静态 Create 方法统一入口,从 network.RentInnerPackInfo() 租赁实例而非直接创建(框架对象池机制)。租赁时自动关联当前网络实例(Network)并重置 IsDisposed 状态,确保每次使用的实例状态干净,避免跨请求的状态污染。

内存流管理的深度实践 :实现 APackInfo 的抽象方法,从网络实例的内存流池(Network.MemoryStreamBufferPool)中租赁 MemoryStreamBuffer。通过 MemoryStream ??= ... 逻辑确保仅在首次调用时获取内存流,避免重复租赁;支持指定初始大小,适配不同数据包的内存需求。配合 Dispose 时的自动归还,形成 "租赁 - 使用 - 回收" 的内存流管理闭环,避免传统 MemoryStream 频繁分配释放导致的性能损耗。

从字节流到业务对象的反序列化链路Deserialize 方法是内网消息解析的 "最后一公里",负责将内存流中的字节数据转换为具体业务对象(IMessage),其逻辑可拆解为五步递进流程,兼顾功能完整性与性能优化:

  1. 内存流有效性校验 :首先检查 MemoryStream 是否为 null(如网络传输失败导致无数据),若为空则输出调试日志并返回 null,避免后续操作出现空引用异常,是防御性编程的典型体现。
  2. 跳过固定头部消息体定位 :内网数据包采用 "头部 + 消息体" 结构(头部含协议号、长度等元信息,长度由 Packet.InnerPacketHeadLength 定义,通常为 20 字节)。通过 MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin) 将流指针移至消息体起始位置,确保后续仅读取业务数据,剥离无关元信息。
  3. 无数据(空消息)场景的高效处理 :当 MemoryStream.Length == 0 时(如心跳包、全默认值对象的 Protobuf 序列化结果),无需反序列化,直接创建 messageType 类型的空实例,首次处理某类型时,通过 CreateInstance.CreateObject(messageType) 反射生成实例创建函数,并缓存到 _createInstances 字典中;后续再遇到该类型,直接从字典获取函数创建实例,避免重复反射的性能损耗,这是 "空间换时间" 的典型优化。
  4. 正常反序列化:多协议动态适配 :若消息体有数据,则通过 SerializerManager 按协议类型(OpCodeIdStruct.OpCodeProtocolType)获取对应序列化器(如 Protobuf、Bson 等),调用其 Deserialize 方法完成字节流到业务对象的转换。转换后通过 MemoryStream.Seek(0, SeekOrigin.Begin) 重置流指针,方便内存流后续复用(如写入新数据)。
  5. 异常处理:协议不支持的容错机制 :若 SerializerManager 找不到对应协议的序列化器,输出含 ProtocolCode 的错误日志(便于定位不支持的协议类型),重置流指针后返回 null,确保异常场景下的程序稳定性与可调试性。

OuterPackInfo:客户端 - 服务器通信的数据包封装

用于客户端与服务器之间的外网通信场景,封装外网数据包的包头信息(如 RpcIdRouteId)和包体数据,适配外网协议的安全性与兼容性要求。源码位于 Runtime/Core/Network/Message/PacketParser/Pack/OuterPackInfo.cs

csharp 复制代码
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
using System;
using System.IO;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.PacketParser.Interface;
using Fantasy.Serialize;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS8603 // Possible null reference return.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8602 // Dereference of a possibly null reference.
namespace Fantasy.PacketParser
{
    public sealed class OuterPackInfo : APackInfo
    {
        public override void Dispose()
        {
            if (IsDisposed)
            {
                return;
            }
            var network = Network;
            base.Dispose();
            network.ReturnOuterPackInfo(this);
        }

        public static OuterPackInfo Create(ANetwork network)
        {
            var outerPackInfo = network.RentOuterPackInfo();
            outerPackInfo.Network = network;
            outerPackInfo.IsDisposed = false;
            return outerPackInfo;
        }

        public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
        {
            if (MemoryStream == null)
            {
                MemoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(memoryStreamBufferSource, size);
            }

            return MemoryStream;
        }

        /// <summary>
        /// 将消息数据从内存反序列化为指定的消息类型实例。
        /// </summary>
        /// <param name="messageType">目标消息类型。</param>
        /// <returns>反序列化后的消息类型实例。</returns>
        public override object Deserialize(Type messageType)
        {
            if (MemoryStream == null)
            {
                Log.Debug("Deserialize MemoryStream is null");
                return null;
            }

            MemoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
            
            if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                var obj = serializer.Deserialize(messageType, MemoryStream);
                MemoryStream.Seek(0, SeekOrigin.Begin);
                return obj;
            }
            
            MemoryStream.Seek(0, SeekOrigin.Begin);
            Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
            return null;
        }
    }
}
OuterPackInfo 的核心功能与实现逻辑:外网数据包的载体与通信适配

对象池化的资源循环机制 :重写 Dispose 方法实现实例与资源的双重回收。通过 IsDisposed 状态避免重复释放,调用基类清理元数据后,将实例通过 network.ReturnOuterPackInfo(this) 归还至对象池,结合 "租赁 - 归还" 模式减少 GC 压力,适配客户端 - 服务器高频交互场景。

基于对象池的高效实例创建机制 :静态 Create 方法为唯一入口,从 network.RentOuterPackInfo() 租赁实例,关联网络实例并重置 IsDisposed 状态,确保实例干净可用,避免跨请求状态污染,复用框架对象池管理逻辑。

内存流管理的实践 :实现 APackInfo 抽象方法,当 MemoryStreamnull 时从 Network.MemoryStreamBufferPool 租赁内存流,否则直接复用,避免重复租赁。配合 Dispose 时的自动归还,形成 "租赁 - 使用 - 回收" 闭环,优化内存分配效率。

从字节流到业务对象的反序列化链路Deserialize 方法完成外网消息的类型转换,流程清晰且适配外网特性:

  • 内存流有效性校验 :检查 MemoryStream 是否为 null,异常时输出日志并返回 null,保障程序稳定性。
  • 跳过外网头部定位消息体 :通过 MemoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin) 跳过外网数据包头部,定位至业务数据(外网头部格式与内网存在差异)。
  • 正常反序列化:多协议适配 :通过 SerializerManager 获取对应协议的序列化器,完成字节流到业务对象的转换后重置流指针,方便内存流复用。
  • 异常处理:协议不支持的容错 :若序列化器不存在,输出含 ProtocolCode 的错误日志,重置流指针后返回 null,确保异常场景的可调试性。

InnerPackInfo 差异:无空消息创建函数缓存(_createInstances),因外网空消息场景极少,简化设计以降低协议交互复杂度。

ProcessPackInfo:进程间通信的数据包封装

基于同一物理机上不同进程间的通信(如游戏主进程与日志进程),封装进程间数据包信息,适配本地进程通信(IPC)的低延迟需求。源码位于 Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs

ini 复制代码
#if FANTASY_NET
using System.Collections.Concurrent;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.PacketParser.Interface;
using Fantasy.Pool;
using Fantasy.Serialize;

// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8603 // Possible null reference return.
namespace Fantasy.PacketParser
{
    public sealed class ProcessPackInfo : APackInfo
    {
        private int _disposeCount;
        public Type MessageType { get; private set; }
        private static readonly ConcurrentQueue<ProcessPackInfo> Caches = new ConcurrentQueue<ProcessPackInfo>();
        private readonly ConcurrentDictionary<Type, Func<object>> _createInstances = new ConcurrentDictionary<Type, Func<object>>();

        public override void Dispose()
        {
            if (--_disposeCount > 0 || IsDisposed)
            {
                return;
            }

            _disposeCount = 0;
            MessageType = null;
            base.Dispose();

            if (Caches.Count > 2000)
            {
                return;
            }

            Caches.Enqueue(this);
        }

        public static unsafe ProcessPackInfo Create<T>(Scene scene, T message, int disposeCount, uint rpcId = 0, long routeId = 0) where T : IRouteMessage
        {
            if (!Caches.TryDequeue(out var packInfo))
            {
                packInfo = new ProcessPackInfo();
            }

            var type = typeof(T);
            var memoryStreamLength = 0;
            packInfo._disposeCount = disposeCount;
            packInfo.MessageType = type;
            packInfo.IsDisposed = false;
            var memoryStream = new MemoryStreamBuffer();
            memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack;
            OpCodeIdStruct opCodeIdStruct = message.OpCode();
            memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);

            if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                serializer.Serialize(type, message, memoryStream);
                memoryStreamLength = (int)memoryStream.Position;
            }
            else
            {
                Log.Error($"type:{type} Does not support processing protocol");
            }

            var opCode = scene.MessageDispatcherComponent.GetOpCode(packInfo.MessageType);
            var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength;

            if (packetBodyCount == 0)
            {
                // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
                // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
                packetBodyCount = -1;
            }

            if (packetBodyCount > Packet.PacketBodyMaxLength)
            {
                // 检查消息体长度是否超出限制
                throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
            }

            var buffer = memoryStream.GetBuffer();

            fixed (byte* bufferPtr = buffer)
            {
                var opCodePtr = bufferPtr + Packet.PacketLength;
                var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation;
                var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation;
                *(int*)bufferPtr = packetBodyCount;
                *(uint*)opCodePtr = opCode;
                *(uint*)rpcIdPtr = rpcId;
                *(long*)routeIdPtr = routeId;
            }

            memoryStream.Seek(0, SeekOrigin.Begin);
            packInfo.MemoryStream = memoryStream;
            return packInfo;
        }

        public unsafe void Set(uint rpcId, long routeId)
        {
            var buffer = MemoryStream.GetBuffer();

            fixed (byte* bufferPtr = buffer)
            {
                var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation;
                var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation;
                *(uint*)rpcIdPtr = rpcId;
                *(long*)routeIdPtr = routeId;
            }

            MemoryStream.Seek(0, SeekOrigin.Begin);
        }

        public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
        {
            throw new NotImplementedException();
        }

        public override object Deserialize(Type messageType)
        {
            if (MemoryStream == null)
            {
                Log.Debug("Deserialize MemoryStream is null");
                return null;
            }

            object obj = null;
            MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);

            if (MemoryStream.Length == 0)
            {
                if (_createInstances.TryGetValue(messageType, out var createInstance))
                {
                    return createInstance();
                }

                createInstance = CreateInstance.CreateObject(messageType);
                _createInstances.TryAdd(messageType, createInstance);
                return createInstance();
            }

            if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer))
            {
                obj = serializer.Deserialize(messageType, MemoryStream);
                MemoryStream.Seek(0, SeekOrigin.Begin);
                return obj;
            }
            
            MemoryStream.Seek(0, SeekOrigin.Begin);
            Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
            return null;
        }
    }
}
#endif
ProcessPackInfo 的核心功能与实现逻辑:进程内消息的专用数据包载体

引用计数式的资源循环机制 :重写 Dispose 方法实现基于引用计数的精准回收。通过 _disposeCount 跟踪实例引用次数,递减至 0 且未标记 IsDisposed 时,清理 MessageType 等元数据,调用基类 Dispose 释放资源,最终将实例加入 Caches 并发队列(最大容量 2000)缓存复用。自建缓存队列而非依赖网络对象池,避免跨组件依赖,适配进程内独立运行场景。

泛型驱动的实例创建与数据包构建 :通过静态泛型 Create<T> 方法统一入口,实现 "实例获取 - 序列化 - 头部构建" 一站式处理。优先从 Caches 队列获取实例,未命中则新建;创建内存流后预留头部空间,通过序列化器将消息写入流中,记录总位置并计算消息体长度(修正空消息为 -1 以适配分包逻辑);最终通过指针操作写入头部元信息(长度、协议号等),并校验消息体长度是否超限。

进程内专属的内存流管理模式 :重写 RentMemoryStream 方法直接抛出异常,因内存流由 Create 方法通过 new MemoryStreamBuffer() 专属创建,无需从外部内存池租赁。这种设计适配进程内消息生命周期短、传递高效的特性,减少对外部组件的依赖,实现独立的内存管控。

并发安全的反序列化链路Deserialize 方法负责将字节流转换为业务对象,逻辑分四步递进:

  1. 内存流有效性校验 :检查 MemoryStream 是否为 null,异常时输出日志并返回 null,保障程序稳定性。
  2. 消息体定位 :通过 MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin) 跳过内网头部,定位至业务数据。
  3. 空消息高效处理 :当内存流长度为 0 时(如全默认值对象的序列化结果),无需反序列化,直接创建 messageType 类型的空实例。首次处理该类型时,通过反射生成实例创建函数并存入 _createInstancesConcurrentDictionary);后续再遇到同一类型,直接调用缓存的函数创建实例,既避免重复反射的性能损耗,又通过线程安全的字典实现并发场景下的安全复用。
  4. 多协议适配与异常处理 :通过 SerializerManager 获取对应序列化器完成转换,重置流指针方便复用;协议不支持时输出含 ProtocolCode 的错误日志,确保可调试性。

头部元信息的高效修改机制Set 方法通过指针直接操作内存流缓冲区,定位至 rpcIdrouteId 对应偏移量修改值,无需重新序列化整个消息,适配进程内路由动态调整场景,大幅提升修改效率。

总结:Fantasy 框架网络数据包解析机制的架构、实现与分布式场景适配价值

Fantasy 框架的网络数据包解析机制以 "抽象定义 - 具体实现 - 工厂调度" 为核心架构,通过 APacketParserAPackInfo 构建抽象契约,结合 Packet 结构体与 OpCodeIdStruct 实现标准化数据包格式与标识体系,针对 KCPTCP 等协议及 WebGL 等环境提供 BufferPacketParserReadOnlyMemoryPacketParser 等场景化解析器,借助 PacketParserFactory 动态调度,形成多协议适配能力,高效解决了分布式网络中的分包粘包、内存管理、跨场景通信等问题,兼顾灵活性、高性能与可靠性,为分布式应用提供了坚实的网络通信底层支撑。

相关推荐
ChinaRainbowSea27 分钟前
7. LangChain4j + 记忆缓存详细说明
java·数据库·redis·后端·缓存·langchain·ai编程
舒一笑27 分钟前
同步框架与底层消费机制解决方案梳理
后端·程序员
minh_coo28 分钟前
Spring框架事件驱动架构核心注解之@EventListener
java·后端·spring·架构·intellij-idea
白初&2 小时前
SpringBoot后端基础案例
java·spring boot·后端
计算机学姐4 小时前
基于Python的旅游数据分析可视化系统【2026最新】
vue.js·后端·python·数据分析·django·flask·旅游
该用户已不存在5 小时前
你没有听说过的7个Windows开发必备工具
前端·windows·后端
David爱编程5 小时前
深入 Java synchronized 底层:字节码解析与 MonitorEnter 原理全揭秘
java·后端
KimLiu5 小时前
LCODER之Python:使用Django搭建服务端
后端·python·django
再学一点就睡5 小时前
双 Token 认证机制:从原理到实践的完整实现
前端·javascript·后端
yunxi_055 小时前
终于搞懂布隆了
后端