笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 引擎骨架

    • UE 引擎(客户端—服务器交互)
    • 引擎架构与一帧的时序
    • UObject / GC / 反射
    • Gameplay Framework 与端存在性矩阵
  • 复制栈底层

    • NetDriver / Channel / Bunch
    • 属性复制
    • RPC
  • 带宽与规模

    • Relevancy 与带宽预算
    • UE5 Iris 复制系统
  • 局内同步

    • 移动预测与回滚
    • 延迟补偿
    • 网络物理同步
  • 系统与运维

    • GAS 网络模型
    • 专用服务器 · Session · Travel
    • 服务器权威与反作弊

RPC(远程过程调用)

属性复制解决「状态同步」,RPC 解决「事件通知」——开枪了、技能放了、按钮按了。UE 的 RPC 有六种组合,用错方向就静默失效,用错可靠性就洪泛断线。本篇把六格矩阵、调用前提、安全校验和顺序陷阱一次讲清。


一句话结论

Server/Client/NetMulticast × Reliable/Unreliable 六格:属性传状态、RPC 传事件,客户端来的参数一律不可信。

场景问题

打个比方:RPC 就像寄信,属性复制像公告板。你要通知「我开枪了」这个一瞬间的事件,寄信(RPC)合适;但「我现在还剩 30 发子弹」这种持续状态,寄信就不行了——后来入场的人收不到之前的信,只能看公告板(属性复制)上贴的当前值。而且信分挂号信(Reliable,丢了重寄、保证到)和平信(Unreliable,丢了算了)。狂寄挂号信会把邮局的待寄队列塞满——UE 里这会直接断开连接。

高频追问:「客户端调 Server RPC,服务器一定会执行吗?」——不一定。只有该 Actor 的 owning client 调才有效,别的客户端调会被静默丢弃。这类「静默失效」是 RPC 最坑的地方。

实现方案

1. 六格矩阵

UFUNCTION(Server, Reliable, WithValidation)   // 客户端调 → 服务器执行
void ServerFire(FVector_NetQuantize AimDir);

UFUNCTION(Client, Reliable)                   // 服务器调 → 那个客户端执行
void ClientNotifyReward(int32 Gold);

UFUNCTION(NetMulticast, Unreliable)           // 服务器调 → 所有相关客户端执行
void MulticastPlayHitFX(FVector Loc);
类型谁调用谁执行ReliableUnreliable
Serverowning client服务器关键操作:开火、购买、使用技能高频输入:移动上报
Client服务器该 owning client私有通知:奖励、踢出提示本地表现:轻量提示
NetMulticast服务器所有相关客户端 + 服务器自己全场必达事件:比赛结束特效音效:命中火花、爆炸

可靠性语义:

  • Reliable:进重发队列,保证到达且同类保序。滥用会 Reliable buffer overflow 断线,见 NetDriver/Channel/Bunch。
  • Unreliable:丢了就丢。适合「过期就没意义」的表现类事件(旧的命中特效补放反而怪)。

2. 调用前提与静默失效

RPC 生效有硬前提,不满足就静默丢弃(不报错,最难查):

情形结果
Actor 没开 bReplicates所有 RPC 全部无效
非 owning client 调 Server RPC丢弃(关键!)
客户端调 NetMulticast只在本地执行,不广播
服务器调 Client RPC 但目标不是 owner无效
NetMulticast 时该 Actor 对某客户端不相关(not relevant)那个客户端不执行,见 Relevancy
在 NM_Standalone(单机)调任何 RPC直接本地执行

owning client 判定靠所有权链:PlayerController 拥有 Pawn,链最终连到一个 NetConnection。详见 Gameplay Framework。

3. 安全:客户端参数一律不可信

UFUNCTION(Server, Reliable, WithValidation)
void ServerBuyItem(int32 ItemId, int32 Count);

// UHT 生成,你必须实现:返回 false → 引擎认为客户端作弊,踢掉连接
bool AMyPC::ServerBuyItem_Validate(int32 ItemId, int32 Count) {
    return Count > 0 && Count <= 99 && IsValidItemId(ItemId);  // 边界校验
}

void AMyPC::ServerBuyItem_Implementation(int32 ItemId, int32 Count) {
    // 到这里参数已过 Validate,但业务合法性仍要服务器自己查:
    if (!HasEnoughGold(ItemId, Count)) return;   // 钱够不够,服务器算
    ...
}
  • WithValidation 生成 _Validate,返回 false 会断开该连接(视为作弊)。
  • _Validate 只做廉价的参数边界校验(范围、非空、枚举合法);业务合法性(钱够不够、CD 到没到)在 _Implementation 里由服务器权威计算。
  • 铁律:任何来自客户端的 Server RPC 参数都是攻击面。别信客户端传来的「伤害值」「命中目标」「最终位置」——只信「输入意图」,结果由服务器算。详见 服务器权威与反作弊。

4. 顺序陷阱与选型

最容易翻车的一组事实:

  • RPC 与属性复制之间无顺序保证。你不能假设「Multicast 播特效时,位置属性已经复制到位」。要么把需要的数据当 RPC 参数带上,要么用 RepNotify 等状态就绪。
  • 可靠 RPC 与不可靠 RPC 之间不保序。只有同类型可靠 RPC 之间保序。
  • NetMulticast 在服务器也执行。写 Multicast 实现时要考虑「服务器也会跑到这」,别在里面重复做服务器已经做过的权威逻辑。
  • 可靠 RPC 洪泛 = 断线。高频事件(每帧的东西)绝不能用可靠 RPC,会把重发队列塞爆踢连接。

选型原则(面试高频):

属性传状态,RPC 传事件。

需求用什么为什么
持续状态(血量、位置、分数)属性复制新连接自动拿到当前值;差分省带宽
一次性事件(开火、命中、技能触发)RPC事件没有「当前值」,是瞬时的
全场必达大事件(比赛结束)NetMulticast, Reliable所有人必须收到
高频表现(脚步、弹壳、火花)NetMulticast, Unreliable 或纯本地丢了无所谓,不能占可靠队列
玩家意图上报(我要移动/开枪)Server RPC服务器权威裁定,参数走 _Validate

带宽对比:可靠 RPC 每次都要占重发队列并可能重传,成本高于不可靠;能不可靠就不可靠,能属性就别 RPC。

5. 一句话记住三种方向的差别

一句话类比
Server客户端上报意图,服务器裁定;只有 owner 有资格发你向裁判申请「我要开枪」,裁判说行不行
Client服务器私聊某一个人给你一个人寄的挂号信:奖励到账
NetMulticast服务器当众广播,所有相关客户端 + 服务器自己都跑广场上的大喇叭:全场都听见,喊话的人自己也听见

三条最容易错的边界:

  • 方向反了就静默丢弃——客户端调 NetMulticast 不会广播,只在本地跑,且不报错。
  • Multicast 服务器也会执行,别在里面重复做已经做过的权威扣血逻辑。
  • 只给一个人的数据别用 Multicast,用 Client RPC 或 COND_OwnerOnly 属性——否则又费带宽又泄露信息。

为什么这么做

分方向 + 分可靠性,是因为游戏事件的语义天然不同:玩家意图必须上报服务器(Server)、私有结果只发本人(Client)、公共表现广播所有人(Multicast);关键事件不能丢(Reliable)、表现事件过期即废(Unreliable)。六格覆盖了这些组合。

owning client 限制 Server RPC,是安全设计:只有拥有这个 Pawn 的连接才能替它下指令,防止 A 客户端替 B 客户端发指令。

_Validate 前置:在执行前用廉价校验挡掉明显非法的参数,且非法即踢,把攻击成本抬高。

为什么别的选择不行

  • 用可靠 RPC 传高频状态:重发队列爆掉断线;且旧值重传到达也没意义。应该用不可靠属性复制。
  • 信任客户端 RPC 参数直接执行:客户端可伪造任意值(改内存、改包),等于把权威交给作弊者。必须服务器重算。见 反作弊。
  • 假设 RPC 和属性有序到达:网络乱序 + 分通道,顺序无保证,依赖顺序的逻辑必然偶发 bug。
  • 用 Multicast 传只给一个人的数据:浪费带宽(广播给所有人)且泄露隐私,应该用 Client RPC 或 COND_OwnerOnly 属性。

沉淀结论

RPC 六格:Server(客户端→服务器)/Client(服务器→owner)/NetMulticast(服务器→所有相关+服务器自己)× Reliable/Unreliable。属性传状态、RPC 传事件。Server RPC 需 owning client + bReplicates,否则静默失效。客户端参数不可信,_Validate 挡边界、服务器算业务。可靠 RPC 洪泛会断线,RPC 与属性/异类型之间不保序。

记忆口诀

六格:Server 客户端调服务器执行 / Client 服务器调 owner 执行 / Multicast 服务器调全体+自己执行
可靠性:Reliable 必达保序占队列 / Unreliable 丢了算了适合表现
静默失效:无 bReplicates 全废 / 非 owner 调 Server RPC 丢弃 / 客户端调 Multicast 只本地
安全:WithValidation 挡边界 / 失败踢连接 / 参数不可信 / 服务器算业务
顺序:RPC 与属性不保序 / 异类型不保序 / Multicast 服务器也跑 / 洪泛断线
原则:属性传状态 / RPC 传事件

内容来源

综合整理。主要参考:Epic 官方文档 RPCs、Actor Communication、UE 源码 DataChannel.cpp(RPC 序列化)、社区分析 Cedric Neukirchen《Multiplayer Network Compendium》、Alex Forsythe 网络系列。

相关专题:可靠队列溢出断线的底层机制见 NetDriver/Channel/Bunch;owning client 的所有权链见 Gameplay Framework;属性 vs RPC 的状态同步侧见 属性复制;参数校验与作弊防护见 服务器权威与反作弊。

自测:合上资料能说清楚吗?

客户端调用一个 Server RPC,服务器一定会执行吗?

参考答案

不一定。前提是:① 该 Actor 开了 bReplicates;② 调用方是该 Actor 的 owning client(所有权链连到它的 NetConnection)。非 owning client 调会被静默丢弃,不报错。这是最常见的「RPC 没生效」原因。

NetMulticast RPC 有什么容易忽略的点?

参考答案

① 服务器自己也会执行 Multicast 实现,别在里面重复做服务器已做的权威逻辑;② 只有对该客户端相关(relevant)的 Actor,Multicast 才会在其上执行,不相关的客户端收不到;③ 客户端调 Multicast 只在本地执行、不广播。

_Validate 该做什么、不该做什么?

参考答案

该做廉价的参数边界校验(范围、非空、枚举合法),返回 false 引擎会踢掉连接(视为作弊)。不该做重的业务合法性判断——钱够不够、CD 到没到这类由 _Implementation 里服务器权威计算。总原则:客户端参数一律不可信,只信输入意图,结果服务器算。

为什么「属性传状态、RPC 传事件」?

参考答案

RPC 是一次性事件,新加入的客户端收不到历史;属性复制天然给新连接发当前值且差分省带宽。用 RPC 传状态会踩三个坑:新连接漏收、可靠 RPC 洪泛断线、RPC 与属性不保序。反过来用属性传瞬时事件也不合适(事件没有「当前值」)。

最近更新: 2026/9/10 11:38
Prev
属性复制