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);
| 类型 | 谁调用 | 谁执行 | Reliable | Unreliable |
|---|---|---|---|---|
Server | owning 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,用
ClientRPC 或COND_OwnerOnly属性——否则又费带宽又泄露信息。
为什么这么做
分方向 + 分可靠性,是因为游戏事件的语义天然不同:玩家意图必须上报服务器(Server)、私有结果只发本人(Client)、公共表现广播所有人(Multicast);关键事件不能丢(Reliable)、表现事件过期即废(Unreliable)。六格覆盖了这些组合。
owning client 限制 Server RPC,是安全设计:只有拥有这个 Pawn 的连接才能替它下指令,防止 A 客户端替 B 客户端发指令。
_Validate 前置:在执行前用廉价校验挡掉明显非法的参数,且非法即踢,把攻击成本抬高。
为什么别的选择不行
- 用可靠 RPC 传高频状态:重发队列爆掉断线;且旧值重传到达也没意义。应该用不可靠属性复制。
- 信任客户端 RPC 参数直接执行:客户端可伪造任意值(改内存、改包),等于把权威交给作弊者。必须服务器重算。见 反作弊。
- 假设 RPC 和属性有序到达:网络乱序 + 分通道,顺序无保证,依赖顺序的逻辑必然偶发 bug。
- 用 Multicast 传只给一个人的数据:浪费带宽(广播给所有人)且泄露隐私,应该用
ClientRPC 或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 与属性不保序。反过来用属性传瞬时事件也不合适(事件没有「当前值」)。