页面加载中
博客快捷键
按住 Shift 键查看可用快捷键
ShiftK
开启/关闭快捷键功能
ShiftA
打开/关闭中控台
ShiftD
深色/浅色显示模式
ShiftS
站内搜索
ShiftR
随机访问
ShiftH
返回首页
ShiftL
友链页面
ShiftP
关于本站
ShiftI
原版/本站右键菜单
松开 Shift 键或点击外部区域关闭
互动
最近评论
暂无评论
标签
寻找感兴趣的领域
暂无标签
    0
    文章
    0
    标签
    8
    分类
    10
    评论
    128
    功能
    深色模式
    标签
    JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
    互动
    最近评论
    暂无评论
    标签
    寻找感兴趣的领域
    暂无标签
      0
      文章
      0
      标签
      8
      分类
      10
      评论
      128
      功能
      深色模式
      标签
      JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
      随便逛逛
      博客分类
      文章标签
      复制地址
      深色模式
      AnHeYuAnHeYu
      Search⌘K
      博客
        暂无其他文档

        Iris 网络复制系统技术分析 - 第十四部分:RPC 系统

        December 29, 202517 分钟 阅读72 次阅读

        📡 Iris 网络复制系统技术分析 - 第十四部分:RPC 系统 (Remote Procedure Call)

        📖 阅读提示:本章深入讲解 Iris 中的 RPC(远程过程调用)系统,包含完整源码解析。


        🎯 14.1 RPC 概述

        💡 什么是 RPC?

        PLAINTEXT
        🎮 日常场景类比:
        
        你(客户端)在游戏中按下"开枪"按钮:
            ↓
        你的电脑需要告诉服务器:"我要开枪了!"
            ↓
        服务器收到消息后,执行开枪逻辑
            ↓
        服务器告诉所有其他玩家:"这个人开枪了!"
            ↓
        其他玩家看到你开枪的动画和效果
        
        这个"告诉远程执行某个函数"的过程,就是 RPC!

        RPC (Remote Procedure Call) = 远程过程调用,让你能够在一台机器上调用另一台机器上的函数。

        🏗️ Iris RPC 架构总览

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────┐
        │                      Iris RPC 系统架构                           │
        ├─────────────────────────────────────────────────────────────────┤
        │  ┌──────────┐                              ┌──────────┐         │
        │  │  客户端   │ ════ Server RPC ═══════════▶│  服务器   │         │
        │  │          │ ◀════ Client RPC ═══════════│          │         │
        │  │          │ ◀════ Multicast RPC ════════│          │         │
        │  └──────────┘                              └──────────┘         │
        │                                                                  │
        │  核心组件:                                                      │
        │  ┌────────────┬──────────────┬─────────────┬────────────────┐   │
        │  │  FNetRPC   │UNetRPCHandler│FNetBlobMgr │ReliableQueue   │   │
        │  │  RPC数据包 │  RPC处理器   │ Blob管理器 │  可靠队列      │   │
        │  └────────────┴──────────────┴─────────────┴────────────────┘   │
        └─────────────────────────────────────────────────────────────────┘

        📁 关键源文件索引

        文件

        路径

        说明

        NetRPC.h/.cpp

        NetBlob/

        FNetRPC 核心类

        NetRPCHandler.cpp

        NetBlob/

        RPC 创建和接收

        NetBlobManager.cpp

        NetBlob/

        发送调度管理

        ReliableNetBlobQueue.cpp

        NetBlob/

        可靠队列


        📞 14.2 RPC 类型详解

        🎮 RPC 类型分类

        RPC 类型

        宏声明

        调用方

        执行方

        典型用途

        Server

        UFUNCTION(Server)

        拥有者客户端

        服务器

        玩家输入

        Client

        UFUNCTION(Client)

        服务器

        拥有者客户端

        私人通知

        Multicast

        UFUNCTION(NetMulticast)

        服务器

        所有客户端

        全局事件

        📝 RPC 声明示例

        CPP
        UCLASS()
        class AMyCharacter : public ACharacter
        {
            GENERATED_BODY()
        public:
            // ═══════════════════════════════════════════════════════════
            // 🖥️ Server RPC - 客户端 → 服务器
            // ═══════════════════════════════════════════════════════════
            UFUNCTION(Server, Reliable, WithValidation)
            void ServerFire(FVector_NetQuantize AimLocation);
            
            // Validate 函数:返回 false 会拒绝 RPC
            bool ServerFire_Validate(FVector_NetQuantize AimLocation)
            {
                // 防作弊检查
                if ((GetWorld()->GetTimeSeconds() - LastFireTime) < FireCooldown)
                    return false;
                if (CurrentAmmo <= 0)
                    return false;
                return true;
            }
            
            void ServerFire_Implementation(FVector_NetQuantize AimLocation)
            {
                LastFireTime = GetWorld()->GetTimeSeconds();
                CurrentAmmo--;
                PerformFire(AimLocation);
                MulticastPlayFireEffect(AimLocation);
            }
            
            // ═══════════════════════════════════════════════════════════
            // 📱 Client RPC - 服务器 → 拥有者客户端
            // ═══════════════════════════════════════════════════════════
            UFUNCTION(Client, Reliable)
            void ClientNotifyDamage(float DamageAmount);
            
            // ═══════════════════════════════════════════════════════════
            // 📢 Multicast RPC - 服务器 → 所有客户端
            // ═══════════════════════════════════════════════════════════
            UFUNCTION(NetMulticast, Unreliable)
            void MulticastPlayFireEffect(FVector_NetQuantize Location);
        };

        🔧 14.3 FNetRPC 核心类深度解析

        📝 类定义

        CPP
        // 源码:NetRPC.hclass FNetRPC final : public FNetObjectAttachment
        {
        public:
            // 函数定位器 - 用于在远程端找到正确的函数
            struct FFunctionLocator
            {
                uint16 DescriptorIndex;  // ReplicationStateDescriptor 索引
                uint16 FunctionIndex;    // MemberFunctionDescriptor 索引
            };
            
            static FNetRPC* Create(
                UReplicationSystem* ReplicationSystem, 
                const FNetBlobCreationInfo& CreationInfo, 
                const FNetObjectReference& ObjectReference, 
                const UFunction* Function, 
                const void* FunctionParameters
            );
            
            void CallFunction(FNetRPCCallContext& Context);
        
        private:
            // RPC 头部大小:24 位 = 最大约 2MB
            static constexpr uint32 HeaderSizeBitCount = 24U;
            static constexpr uint32 MaxRpcSizeInBits = (1U << HeaderSizeBitCount) - 1U;
        
            FFunctionLocator FunctionLocator;
            const UFunction* Function;
            TWeakObjectPtr<UObject> ObjectPtr;
        };

        🔍 FFunctionLocator 工作原理

        PLAINTEXT
        FReplicationProtocol
        └── ReplicationStateDescriptors[]
            ├── [0] Descriptor A
            │   └── MemberFunctionDescriptors[]
            │       ├── [0] Function_A1
            │       └── [1] Function_A2
            └── [1] Descriptor B  ◀── DescriptorIndex = 1
                └── MemberFunctionDescriptors[]
                    ├── [0] Function_B1
                    └── [1] ServerFire  ◀── FunctionIndex = 1

        📦 FNetRPC::Create() 源码解析

        CPP
        // 源码:NetRPC.cppFNetRPC* FNetRPC::Create(
            UReplicationSystem* ReplicationSystem, 
            const FNetBlobCreationInfo& CreationInfo, 
            const FNetObjectReference& ObjectReference, 
            const UFunction* Function, 
            const void* FunctionParameters){
            // 步骤 1:获取最顶层的父函数
            while (const UFunction* SuperFunction = Function->GetSuperFunction())
            {
                Function = SuperFunction;
            };
        
            // 步骤 2:获取函数定位器
            FFunctionLocator FunctionLocator = {};
            const FReplicationStateMemberFunctionDescriptor* FunctionDescriptor = nullptr;
            if (!NetRPC_GetFunctionLocator(ReplicationSystem, ObjectReference, Function, 
                                            FunctionLocator, FunctionDescriptor))
            {
                return nullptr;
            }
        
            // 步骤 3:量化函数参数
            const FReplicationStateDescriptor* BlobDescriptor = FunctionDescriptor->Descriptor;
            FQuantizedBlobState QuantizedBlobState;
        
            if (BlobDescriptor != nullptr && BlobDescriptor->InternalSize)
            {
                QuantizedBlobState = FQuantizedBlobState(
                    BlobDescriptor->InternalSize, 
                    BlobDescriptor->InternalAlignment
                );
        
                FNetSerializationContext Context;
                FInternalNetSerializationContext InternalContext(ReplicationSystem);
                Context.SetInternalContext(&InternalContext);
        
                // 🔑 关键:将原始参数量化为网络传输格式
                FReplicationStateOperations::Quantize(
                    Context, 
                    QuantizedBlobState.GetStateBuffer(),
                    static_cast<const uint8*>(FunctionParameters),
                    BlobDescriptor
                );
            }
        
            // 步骤 4:创建 FNetRPC 实例
            FNetRPC* NetRPC = new FNetRPC(CreationInfo);
            NetRPC->SetFunctionLocator(FunctionLocator);
            NetRPC->Function = Function;
            
            // 步骤 5:收集对象引用(用于导出)
            if (BlobDescriptor != nullptr && BlobDescriptor->HasObjectReference())
            {
                FNetReferenceCollector Collector(
                    ENetReferenceCollectorTraits::OnlyCollectReferencesThatCanBeExported
                );
                FReplicationStateOperationsInternal::CollectReferences(
                    LocalContext, Collector, InitStateChangeMaskInfo, 
                    QuantizedBlobState.GetStateBuffer(), BlobDescriptor
                );
        
                if (Collector.GetCollectedReferences().Num())
                {
                    NetRPC->ReferencesToExport = MakeUnique<FNetRPCExportsArray>();
                    for (const auto& Info : Collector.GetCollectedReferences())
                    {
                        NetRPC->ReferencesToExport->AddUnique(Info.Reference);
                    }
                    NetRPC->CreationInfo.Flags |= ENetBlobFlags::HasExports;
                }
            }
        
            if (BlobDescriptor != nullptr)
            {
                NetRPC->SetState(BlobDescriptor, MoveTemp(QuantizedBlobState));
            }
        
            return NetRPC;
        }

        🔄 14.4 RPC 发送流程

        📤 完整发送流程

        PLAINTEXT
        1️⃣ 游戏代码调用 RPC
           ServerFire(AimLocation);
                 │
                 ▼
        2️⃣ UReplicationSystem::SendRPC
           转发到 NetBlobManager
                 │
                 ▼
        3️⃣ FNetBlobManager::SendMulticastRPC/SendUnicastRPC
           • 验证 RPC 方向
           • 获取 RPC 所有者信息
           • 检查休眠状态
           • 调用 RPCHandler->CreateRPC()
                 │
                 ▼
        4️⃣ UNetRPCHandler::CreateRPC
           • 设置 Reliable/Ordered 标志
           • 调用 FNetRPC::Create()
                 │
                 ▼
        5️⃣ AttachmentSendQueue.Enqueue
           • 加入发送队列等待处理

        📝 SendMulticastRPC 核心源码

        CPP
        // 源码:NetBlobManager.cppbool FNetBlobManager::SendMulticastRPC(
            const FSendRPCContext& Context, 
            const void* Parameters, 
            ENetObjectAttachmentSendPolicyFlags SendFlags){
            // 检查 1:RPC 功能是否启用
            if (CVarEnableIrisRPCs.GetValueOnGameThread() <= 0)
                return false;
        
            // 检查 2:RPC 方向验证
            if ((Context.Function->FunctionFlags & 
                 (bIsServer ? (FUNC_NetClient | FUNC_NetMulticast) : FUNC_NetServer)) == 0)
            {
                checkf(false, TEXT("Trying to call RPC %s in the wrong direction."), 
                       ToCStr(Context.Function->GetName()));
                return true;
            }
        
            // 检查 3:是否有有效连接
            const FNetBitArray& ValidConnections = Connections->GetValidConnections();
            if (ValidConnections.FindFirstOne() == FNetBitArray::InvalidIndex)
                return true;
        
            // 获取 RPC 所有者信息
            FRPCOwner OwnerInfo;
            if (!GetRPCOwner(OwnerInfo, Context))
                return false;
            
            // 检查休眠状态
            const bool bIsObjectDormant = 
                NetRefHandleManager->GetWantToBeDormantInternalIndices().IsBitSet(OwnerInfo.RootObjectIndex);
            if (!bAllowRPCsOnDormantObjects && bIsObjectDormant)
            {
                if (!NetRefHandleManager->GetDormantObjectsPendingFlushNet().IsBitSet(OwnerInfo.RootObjectIndex))
                    return false;
            }
        
            // 创建 RPC
            const TRefCountPtr<FNetRPC>& RPC = Handler->CreateRPC(
                OwnerInfo.CallerRef, Context.Function, Parameters);
            if (!RPC.IsValid())
                return true;
        
            // 休眠对象自动 NetFlush
            if (bAutoNetFlushOnDormantRPC && bIsObjectDormant)
            {
                UObjectReplicationBridge* Bridge = 
                    ReplicationSystem->GetReplicationBridgeAs<UObjectReplicationBridge>();
                Bridge->NetFlushDormantObject(
                    ObjectReferenceCache->GetObjectReferenceHandleFromObject(Context.RootObject));
            }
        
            // 加入发送队列
            RPC->SetNetObjectReference(OwnerInfo.CallerRef, OwnerInfo.TargetRef);
            AttachmentSendQueue.Enqueue(
                OwnerInfo.RootObjectIndex, OwnerInfo.SubObjectIndex, 
                reinterpret_cast<const TRefCountPtr<FNetObjectAttachment>&>(RPC), 
                SendFlags, Connections->GetOpenConnections());
        
            return true;
        }

        📝 UNetRPCHandler::CreateRPC 源码

        CPP
        // 源码:NetRPCHandler.cppTRefCountPtr<FNetRPC> UNetRPCHandler::CreateRPC(
            const FNetObjectReference& ObjectReference, 
            const UFunction* Function, 
            const void* Parameters) const{
            FNetBlobCreationInfo CreationInfo;
            CreationInfo.Type = GetNetBlobType();
            
            // 根据函数标志设置可靠性
            CreationInfo.Flags = ((Function->FunctionFlags & FUNC_NetReliable) != 0) 
                ? ENetBlobFlags::Reliable 
                : ENetBlobFlags::None;
            
            // 单播 RPC 需要保序(Multicast 不保序)
            if ((Function->FunctionFlags & FUNC_NetMulticast) == 0)
            {
                CreationInfo.Flags |= ENetBlobFlags::Ordered;
            }
        
            FNetRPC* RPC = FNetRPC::Create(ReplicationSystem, CreationInfo, 
                                            ObjectReference, Function, Parameters);
            return RPC;
        }

        🎯 RPC 发送策略

        CPP
        // 源码:ReplicationSystemTypes.henum class ENetObjectAttachmentSendPolicyFlags : uint32
        {
            None = 0,
            ScheduleAsOOB = 1U << 0U,       // 带外通道,尽快发送
            SendInPostTickDispatch = 1U << 1U,  // PostTick 期间发送
            SendImmediate = ScheduleAsOOB | SendInPostTickDispatch,  // 立即发送
        };

        📥 14.5 RPC 接收流程

        📥 完整接收流程

        PLAINTEXT
        1️⃣ 网络数据到达
           [网络层] → [数据包] → [NetBlob 反序列化]
                 │
                 ▼
        2️⃣ FNetRPC::Deserialize
           • 读取头部(RPC 大小)
           • 反序列化函数定位器
           • 反序列化对象引用
                 │
                 ▼
        3️⃣ ResolveFunctionAndObject
           • 验证对象引用有效
           • 获取函数描述符
           • 恢复 NetBlobFlags
                 │
                 ▼
        4️⃣ UNetRPCHandler::OnNetBlobReceived
           • 创建调用上下文
           • 调用 RPC->CallFunction()
                 │
                 ▼
        5️⃣ FNetRPC::CallFunction
           • 多重安全检查
           • 反量化参数
           • Object->ProcessEvent()

        🔒 FNetRPC::CallFunction 核心源码

        CPP
        // 源码:NetRPC.cppvoid FNetRPC::CallFunction(FNetRPCCallContext& CallContext){
            FNetSerializationContext& Context = CallContext.GetNetSerializationContext();
            const UReplicationSystem* ReplicationSystem = Context.GetInternalContext()->ReplicationSystem;
        
            // ═══════════════════════════════════════════════════════════
            // 安全检查 1:对象存在性
            // ═══════════════════════════════════════════════════════════
            UObject* Object = ObjectPtr.Get();
            if (Object == nullptr)
                Object = NetRPC_GetObject(Context, NetObjectReference, TargetObjectReference);
        
            if (Object == nullptr || Function == nullptr)
            {
                UE_LOG(LogIrisRpc, Error, TEXT("Rejected RPC %s due to missing object or function"),
                       *(Function ? Function->GetName() : TEXT("Unknown")));
                return;
            }
        
            // ═══════════════════════════════════════════════════════════
            // 安全检查 2:网络函数标志
            // ═══════════════════════════════════════════════════════════
            if ((Function->FunctionFlags & FUNC_Net) == 0)
            {
                UE_LOG(LogIrisRpc, Error, TEXT("Rejected function %s - not a Net function"), 
                       ToCStr(Function->GetName()));
                Context.SetError(NetError_FunctionCallNotAllowed);
                return;
            }
        
            // ═══════════════════════════════════════════════════════════
            // 安全检查 3:调用方向验证
            // ═══════════════════════════════════════════════════════════
            const bool bIsServer = ReplicationSystem->IsServer();
            if (bIsServer)
            {
                // 服务器不能执行 Client/Multicast RPC
                if ((Function->FunctionFlags & (FUNC_NetClient | FUNC_NetMulticast)) != 0)
                {
                    UE_LOG(LogIrisRpc, Error, TEXT("Rejected client RPC %s on server"), 
                           ToCStr(Function->GetName()));
                    Context.SetError(NetError_FunctionCallNotAllowed);
                    return;
                }
        
                // ═══════════════════════════════════════════════════════
                // 安全检查 4:所有权验证
                // ═══════════════════════════════════════════════════════
                if (!IsServerAllowedToExecuteRPC(Context))
                {
                    UE_LOG(LogIrisRpc, Error, TEXT("Rejected RPC %s - access denied"), 
                           ToCStr(Function->GetName())); 
                    return;
                }
            }
            else
            {
                // 客户端不能执行 Server RPC
                if ((Function->FunctionFlags & FUNC_NetServer) != 0)
                {
                    UE_LOG(LogIrisRpc, Error, TEXT("Rejected server RPC %s on client"), 
                           ToCStr(Function->GetName()));
                    return;
                }
            }
        
            // ═══════════════════════════════════════════════════════════
            // 准备函数参数
            // ═══════════════════════════════════════════════════════════
            uint8* FunctionParameters = nullptr;
            if (Function->ParmsSize > 0)
            {
                FunctionParameters = static_cast<uint8*>(FMemory_Alloca(Function->ParmsSize));
                FMemory::Memzero(FunctionParameters, Function->ParmsSize);
                
                // 初始化非平凡构造的参数
                // ... (省略初始化代码)
                
                // 🔑 关键:反量化参数
                FReplicationStateOperations::Dequantize(
                    Context, FunctionParameters, 
                    QuantizedBlobState.GetStateBuffer(), BlobDescriptor);
            }
        
            // ═══════════════════════════════════════════════════════════
            // 查找实际函数(支持派生类重写)
            // ═══════════════════════════════════════════════════════════
            const UFunction* ActualFunction = Object->FindFunction(Function->GetFName());
            if (ActualFunction == nullptr)
                ActualFunction = Function;
        
            // ═══════════════════════════════════════════════════════════
            // 🎯 执行函数!
            // ═══════════════════════════════════════════════════════════
            {
                UE::Net::FScopedNetContextRPC CallingRPC;
                Object->ProcessEvent(const_cast<UFunction*>(ActualFunction), FunctionParameters);
            }
        
            // 清理参数
            // ... (省略清理代码)
        }

        🔒 所有权验证

        CPP
        // 源码:NetRPC.cppbool FNetRPC::IsServerAllowedToExecuteRPC(FNetSerializationContext& Context) const{
            const FNetRefHandle Handle = NetObjectReference.GetRefHandle();
            const UReplicationSystem* ReplicationSystem = Context.GetInternalContext()->ReplicationSystem;
        
            // 获取对象的拥有连接 ID
            const uint32 OwningConnectionId = ReplicationSystem->GetOwningNetConnection(Handle);
            // 获取发送 RPC 的连接 ID
            const uint32 ExecutingConnectionId = Context.GetLocalConnectionId();
            
            // 只有对象的拥有者才能调用 Server RPC
            return OwningConnectionId == ExecutingConnectionId;
        }

        🔒 14.6 可靠性与顺序保证

        💡 ENetBlobFlags 详解

        CPP
        // 源码:NetBlob.henum class ENetBlobFlags : uint32
        {
            None = 0,
            Reliable = 1U << 0U,     // 🔒 可靠传输
            RawDataNetBlob = 1U << 1U,  // 原始数据标志
            HasExports = 1U << 2U,   // 有导出引用
            Ordered = 1U << 3U,      // 📋 保序传输
        };

        🔄 FReliableNetBlobQueue 工作原理

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────┐
        │              FReliableNetBlobQueue 内部结构                      │
        ├─────────────────────────────────────────────────────────────────┤
        │  常量:MaxUnackedBlobCount = 256                                │
        │                                                                  │
        │  发送端队列(环形缓冲区):                                      │
        │  ┌────┬────┬────┬────┬────┬────┬────┬────┐                     │
        │  │ B0 │ B1 │ B2 │ B3 │ B4 │ B5 │ B6 │ B7 │  ...                │
        │  └────┴────┴────┴────┴────┴────┴────┴────┘                     │
        │    ▲                   ▲                                        │
        │  FirstSeq            LastSeq                                    │
        │                                                                  │
        │  状态追踪:                                                      │
        │  Sent[]  = [1,1,1,0,0,0,0,0]  // 已发送                        │
        │  Acked[] = [1,1,0,0,0,0,0,0]  // 已确认                        │
        └─────────────────────────────────────────────────────────────────┘

        📦 ACK/NAK 处理源码

        CPP
        // 源码:ReliableNetBlobQueue.cppvoid FReliableNetBlobQueue::ProcessPacketDeliveryStatus(
            EPacketDeliveryStatus Status, 
            const FReplicationRecord& Record){
            switch (Status)
            {
            case EPacketDeliveryStatus::Delivered:
                OnPacketDelivered(Record);
                break;
            case EPacketDeliveryStatus::Lost:
                OnPacketDropped(Record);
                break;
            case EPacketDeliveryStatus::Discard:
                OnPacketDelivered(Record);  // 丢弃但视为已送达
                break;
            }
        }
        
        void FReliableNetBlobQueue::OnPacketDelivered(const FReplicationRecord& Record){
            // 标记 Blob 为已确认
            for (uint32 SeqIt = 0; SeqIt < MaxWriteSequenceCount; ++SeqIt)
            {
                const uint32 Count = Record.Sequences[SeqIt].Count;
                for (uint32 Seq = Record.Sequences[SeqIt].Number, EndSeq = Seq + Count; 
                     Seq != EndSeq; ++Seq)
                {
                    const uint32 Index = SequenceToIndex(Seq);
                    SetIndexIsAcked(Index);
                    NetBlobs[Index].SafeRelease();  // 释放内存
                }
            }
            PopInOrderAckedBlobs();
        }
        
        void FReliableNetBlobQueue::OnPacketDropped(const FReplicationRecord& Record){
            // 标记 Blob 为未发送(需要重传)
            for (uint32 SeqIt = 0; SeqIt < MaxWriteSequenceCount; ++SeqIt)
            {
                const uint32 Count = Record.Sequences[SeqIt].Count;
                UnsentBlobCount += Count;
                for (uint32 Seq = Record.Sequences[SeqIt].Number, EndSeq = Seq + Count; 
                     Seq != EndSeq; ++Seq)
                {
                    ClearSequenceIsSent(Seq);  // 清除已发送标记
                }
            }
        }

        📊 可靠性对比

        特性

        Reliable

        Unreliable

        Ordered

        保证送达

        ✅

        ❌

        ❌

        保证顺序

        ✅

        ❌

        ✅

        丢包重传

        ✅

        ❌

        ❌


        📄 14.7 大型 RPC 分片传输

        💡 为什么需要分片?

        PLAINTEXT
        原始 RPC 数据:50KB
        网络包最大大小:1.5KB
        ❌ 问题:无法一次性发送!
        
        ✅ 解决方案:分片传输
        ┌────┐ ┌────┐ ┌────┐     ┌────┐
        │ P0 │ │ P1 │ │ P2 │ ... │ P33│
        │1.5K│ │1.5K│ │1.5K│     │0.5K│
        └────┘ └────┘ └────┘     └────┘

        📦 分片处理源码

        CPP
        // 源码:PartialNetObjectAttachmentHandler.cppbool UPartialNetObjectAttachmentHandler::PreSerializeAndSplitNetBlob(
            uint32 ConnectionId, 
            const TRefCountPtr<FNetObjectAttachment>& Blob, 
            TArray<TRefCountPtr<FNetBlob>>& OutPartialBlobs, 
            bool bSerializeWithObject){
            // 确定分片阈值
            uint32 BitCountSplitThreshold = GetConfig()->GetBitCountSplitThreshold() & ~31U;
            
            const bool bIsReliable = EnumHasAnyFlags(
                Blob->GetCreationInfo().Flags, ENetBlobFlags::Reliable);
            if (!bIsReliable)
            {
                BitCountSplitThreshold = (ReplicationSystem->IsServer() 
                    ? GetConfig()->GetServerUnreliableBitCountSplitThreshold() 
                    : GetConfig()->GetClientUnreliableBitCountSplitThreshold()) & ~31U;
            }
        
            // 尝试序列化
            TArray<uint32> Payload;
            Payload.AddUninitialized(BitCountSplitThreshold / 32U);
            FNetBitStreamWriter Writer;
            Writer.InitBytes(Payload.GetData(), Payload.Num() * 4U);
        
            // ... 序列化代码 ...
        
            // 判断是否需要分片
            if (Writer.IsOverflown())
            {
                // 需要分片
                return Super::SplitNetBlob(SerializationContext, 
                    reinterpret_cast<const TRefCountPtr<FNetBlob>&>(Blob), OutPartialBlobs);
            }
            else
            {
                // 不需要分片
                FShrinkWrapNetObjectAttachment* ShrinkWrap = 
                    new FShrinkWrapNetObjectAttachment(SerializationContext, Blob, 
                                                       MoveTemp(Payload), Writer.GetPosBits());
                OutPartialBlobs.AddDefaulted_GetRef() = ShrinkWrap;
                return true;
            }
        }

        🎮 14.8 实际应用案例

        🎯 案例:FPS 武器系统

        CPP
        UCLASS()
        class AWeapon : public AActor
        {
            GENERATED_BODY()
        public:
            // Server RPC:请求开火
            UFUNCTION(Server, Reliable, WithValidation)
            void ServerFire(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection);
            
            bool ServerFire_Validate(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection)
            {
                if (GetWorld()->GetTimeSeconds() - LastFireTime < FireCooldown)
                    return false;
                if (CurrentAmmo <= 0)
                    return false;
                return true;
            }
            
            void ServerFire_Implementation(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection)
            {
                LastFireTime = GetWorld()->GetTimeSeconds();
                CurrentAmmo--;
                
                FHitResult HitResult;
                if (GetWorld()->LineTraceSingleByChannel(HitResult, MuzzleLocation, 
                    MuzzleLocation + AimDirection * MaxRange, ECC_Weapon))
                {
                    if (AActor* HitActor = HitResult.GetActor())
                    {
                        UGameplayStatics::ApplyDamage(HitActor, Damage, nullptr, this, nullptr);
                        MulticastOnHit(HitResult.Location, HitResult.Normal);
                    }
                }
                MulticastOnFire(MuzzleLocation, AimDirection);
            }
            
            // Multicast RPC:播放开火效果
            UFUNCTION(NetMulticast, Unreliable)
            void MulticastOnFire(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection);
            
            void MulticastOnFire_Implementation(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection)
            {
                if (MuzzleFlashParticle)
                    UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlashParticle, MuzzleLocation);
                if (FireSound)
                    UGameplayStatics::PlaySoundAtLocation(this, FireSound, MuzzleLocation);
            }
        };

        🛠️ 14.9 最佳实践

        ✅ 最佳实践清单

        实践

        说明

        使用 WithValidation

        Server RPC 必须验证参数防作弊

        合理选择可靠性

        重要操作用 Reliable,频繁更新用 Unreliable

        使用量化类型

        FVector_NetQuantize 减少带宽

        避免大型 RPC

        大数据考虑分片或属性复制

        ⚠️ 常见陷阱

        陷阱

        解决方案

        RPC 不执行

        检查对象是否已复制、函数标志是否正确

        参数不同步

        确保参数类型支持序列化

        频繁 RPC 导致延迟

        合并 RPC、使用属性复制


        📋 14.10 总结

        🎯 核心概念

        概念

        说明

        FNetRPC

        RPC 数据包,继承自 FNetObjectAttachment

        FFunctionLocator

        函数定位器,通过索引找到远程函数

        UNetRPCHandler

        RPC 处理器,创建和接收 RPC

        FReliableNetBlobQueue

        可靠传输队列

        📊 RPC 类型速查

        类型

        方向

        可靠性

        用途

        Server

        客户端→服务器

        通常 Reliable

        玩家操作

        Client

        服务器→客户端

        视情况

        私人通知

        Multicast

        服务器→所有

        视情况

        全局事件


        本文档基于 Unreal Engine 5.5.0 Iris 源代码分析(源码目录:Engine/Source/Runtime/Experimental/Iris/)

        最后更新于 April 13, 2026
        On this page
        暂无目录