云游戏技术专题全层级16 / 30 已发布

云游戏专用传输协议设计:私有协议对抗开放标准

· 0 分钟阅读
云游戏专用传输协议设计:私有协议对抗开放标准 Private Transport Protocols vs Open Standards — 云游戏技术专题

云游戏专用传输协议设计:私有协议对抗开放标准 Private Transport Protocols vs Open Standards

为什么传输协议是云游戏体验的最后一道闸门

先讲个我亲身经历的场景。前年去一家做云游戏 Demo 的小团队看测试,测速软件挂在副屏上:下行 200Mbps,ping 12ms,丢包 0%。一切漂亮得像体检报告全绿。然后策划把手柄接上,推了一下右摇杆,画面里的人物转了个身——像在糖浆里转身。会议室安静了三秒,老板转头问技术负责人:是不是服务器配置不够?

真不是。带宽够,延迟数字也不难看,问题出在这条链路上最不起眼、也最容易被当成「基础设施」跳过的一环:传输协议。

这个系列前面几篇我们拆了编解码([02])、延迟的完整分量图谱([03])、抖动和丢包怎么毁体验([09])、WebRTC 的拥塞控制为什么不好用([11])、QUIC 能不能重构传输层([12])。这些讲的都是「零件」。这一篇讲的是把这些零件焊在一起的那根骨架——当你要从头设计一条「只服务游戏」的通道,你会怎么切分它、给它定什么目标函数、在哪些地方必须跟通用标准分道扬镳。

为什么说它是「最后一道闸门」?因为前面所有环节的努力,都要靠它送达。你编码器调得再好,超分模型再神,一个 jitter buffer 设错,玩家感受到的就是「糊」和「黏」。反过来,一条设计对路的协议,能让一颗中端 GPU 的输出在玩家手里显得比实际更利索。

而这条闸门最有趣的地方在于:它几乎是云游戏行业唯一一块「大家宁愿自己造轮子」的地方。Parsec 自己写,NVIDIA 的 GameStream 自己写,Valve 的 Steam Link 自己写,Google 当年做 Stadia 也是拿 WebRTC 大改而不是直接用。开放标准就在那儿摆着,免费的、成熟的、有 RFC 编号的,为什么不用?

答案就一句话,本文剩下的部分都在解释它:开放标准优化的是平均吞吐,私有协议优化的是最差那一帧的到达时刻。这两个目标函数,在数学上就不是一回事。

第一层:游戏串流传的不是「视频流」,是「帧的因果链」

我们先把这个认知掰正。你打开一个视频网站,它传的是「一段内容」,你看的时候可以缓冲三秒、可以卡一下再追上来,只要总体连续,你的体验就是好的。所以 RTP、HLS、DASH 这一整套东西,底层假设都是「平滑播放优先,延迟可以让步」。

云游戏完全反过来。它传的不是内容,是因果。玩家的手在环里:按下 → 服务端渲染 → 编码 → 传输 → 解码 → 显示,这个环闭合得越快,玩家越觉得「这是我的角色」。任何一帧都不允许「晚点再到」,因为它的价值跟时间强绑定,迟到一帧就等于没到——你总不能在玩家已经松开摇杆之后,再补放他半秒前推的那一下。

所以云游戏协议的第一个设计动作,是把「流」拆成「帧」,并且给每一帧打上三样东西:

  • 帧序号——不是包序号,是帧序号。这个区别是整篇文章的地基。
  • 采集时刻——这一帧是什么时候被渲染出来的,用来算端到端延迟。
  • 依赖关系——这是 I 帧还是 P 帧,它参考了哪几帧。

为什么非要区分包序号和帧序号?因为一个 1080p60 的帧,切碎之后是几十个 UDP 包。丢掉其中任何一个包,这一整帧就废了。而通用的传输层只看得到「包」,它不知道这几十个包是「一荣俱荣、一损俱损」的一伙人。你用包级的重传去救它,救回来一个包,另外三个还在路上,等齐了这帧早就过期了。

这就是为什么私有协议普遍把可靠性单位从包提升到帧。它先问:这一帧还来得及吗?来不及,直接扔,把带宽省给下一帧;来得及,那就一口气把它缺的包都补上。这个判断听着简单,但它要求协议栈知道「帧的 deadline」,而 WebRTC 里的 RTP 头是没有这个概念的位置的——RTP 有扩展头可以塞,但你要自己在 RTCP 里做一套时间映射,绕得人脑壳疼。

jitter buffer:一个必须被压到接近零的组件

通用实时通信里最受宠的组件是抖动缓冲区(jitter buffer),它的活是「把忽快忽慢到达的包捋顺了再交给解码器」。通话和视频会议里这东西开到 100~200ms 都算正常,用户感知不到。

云游戏里它是天敌。因为它每多存一帧,玩家的手感就多钝一分。所以云游戏协议的 jitter buffer 目标值基本是「0 到 1 帧」,甚至干脆取消这个环节,改成「收到就解,不齐就丢」。

这里我要坦白一次翻车。早些年做局域网串流的时候,我天真地认为「buffer 设 0 一定最爽」,结果在一台 Wi-Fi 信号两格的笔记本上,画面碎成了马赛克拼贴画——因为网络抖动被完整地传给了显示层,一帧早到一帧晚到,解码器反复等参考帧。后来我们改成「buffer 长度动态跟随抖动方差的 1.2 倍,但硬上限 1 帧」,画面才稳下来。教训是:延迟不是越低越好,是「可预测」比「低」更重要。玩家能适应稳定的 40ms,适应不了在 20ms 和 80ms 之间反复横跳。

Parsec 的帧级闭环:一个值得抄的思路

Parsec 是这条路上最早把「帧级反馈」做成产品级体验的团队之一(2021 年被 Unity 收购)。它的核心机制,用社区逆向和官方文档能拼出来的大致是这样的:客户端不是按包回报收没收到,而是按帧回报「我已经显示到第几帧了」。这个数字一路回传,宿主机拿到之后就有了一把手感标尺——它同时涵盖了编码耗时、网络传输、解码排队、显示刷新,一锅烩成一个数。

这个设计妙在哪?妙在它绕开了「我到底该优化哪一段」的纠结。你不需要精确知道延迟分布在哪,你只需要知道「玩家现在看到的画面,比我渲染的晚了 5 帧」,然后所有策略——降码率、丢帧、调编码参数——都围绕把那个数字压回去。这是一个典型的策划思维:与其测量每一段,不如直接测「玩家感知到的那一段」。

代价是什么?代价是你放弃了细粒度的诊断能力。帧级反馈告诉你「慢了」,但不告诉你「为什么慢」。所以 Parsec 这类产品在客户端一般还会维护一个更细的统计面板给进阶用户看,把它当成调试工具,而不是控制回路的一部分。

协议组织方式可靠性单位时间语义典型适用场景
RTP / WebRTC包有缓冲区,平滑优先视频会议、直播、通话
HLS / DASH分片(2~6 秒)延迟完全让步点播、长视频
游戏串流私有协议帧帧级 deadline,迟到即作废云游戏、远程桌面、串流主机

这张表就一句话能概括:越往下,时间越硬,能容忍的平均质量损失越大。云游戏愿意为了「准时」牺牲「好看」,这是它跟所有其他视频应用最根本的分歧。

第二层:丢包之后怎么办——重传、FEC,还是干脆扔掉

网络一定会丢包,这点没人能躲开。丢包之后你只有三条路:重传(ARQ)、前向纠错(FEC)、丢弃并掩盖。私有协议的差异,基本就是这三条路怎么配比。

重传的隐藏前提:RTT 必须小于帧预算

重传的逻辑听着最直观:没收到?我再发一遍。但它在游戏串流里有个致命的前提条件——你得在一个帧的时间内完成「发现丢包 → 请求 → 重传 → 到达」这一个来回。60fps 意味着一帧 16.7ms。哪怕 RTT 只有 30ms,你重传的包到的时候,这一帧已经晚了将近两帧,属于无效劳动,只是白白占用了带宽,让后面的帧更堵。

所以业界的做法叫「选择性重传」:只在「这一帧还没撞线」的窄窗口里重传,而且只重传那些「缺了它整帧就废」的包,不是所有丢包都补。这个窗口窄到什么程度?大概是「从丢包被检测到,到这一帧 deadline 还剩多少时间」,通常也就是几毫秒到十几毫秒。窗口一过,一刀切掉。

这套机制听上去很克制,但它是正确的克制。我见过一些自研团队一上来就「丢包必重传」,结果在高丢包网络下画质不升反降——因为重传包和正常包抢同一条瓶颈链路,排队延迟一涨,连锁反应把本来能到的帧也拖死了。

Moonlight / Sunshine:一个教科书级的「死亡螺旋」案例

要讲清楚重传、FEC 和丢弃之间的取舍,没有比 Moonlight + Sunshine 这对组合更好的样本了。这套东西的来历本身就很有戏剧性:它逆向的是 NVIDIA GameStream 协议,而这个协议是 RTSP 负责控制、RTP 负责视频的老架构。NVIDIA 在 2022 年底宣布关停 GameStream,社区转头把服务端也自己写了一遍,就是 Sunshine。

这套协议里有两个参数,值得任何做串流的人拿小本本记下来:

第一个是 FEC 冗余率。Sunshine 的配置文件里有个叫 fec_percentage 的项,默认值量级在 20% 上下,意思是「每发 100 个数据包,额外塞 20 个校验包」。它是用 Reed-Solomon 这类纠删码做的,好处是不需要等一个 RTT——丢了 5 个包,只要冗余包够,接收端自己就能把数据算回来。代价是带宽实打实多花 20%。在局域网或者同城节点,这买卖很划算;在跨省 4G 上,你多花的 20% 带宽可能正好压垮那条本来就只有 8Mbps 可用的链路。

第二个是「请求 IDR」。IDR 就是完整的关键帧。当接收端判断画面已经烂到没法看,它会通过控制通道向宿主机请求「给我来个整帧」。这招很有效,但它埋着一颗雷:一个关键帧的体积,通常是普通 P 帧的 5 到 10 倍。你请求一个 IDR,等于瞬间往那条正在丢包的链路里灌进十倍的流量。于是链路更堵,于是丢更多包,于是更多客户端请求 IDR。这就是社区里被反复讨论的「IDR 风暴」——一个负反馈循环,能把整条链路拖垮。

后来 Sunshine 社区的处理办法很聪明,也很有代表性:给 IDR 请求加限流和最小间隔,同时提供「渐进式刷新」的选项——不一次性发整帧,而是让编码器在若干帧内逐步把整幅画面刷一遍。慢一点,但不会制造尖峰。这个取舍的本质是:用「慢一点恢复」换「不制造新的拥塞」。放到玩家体验上,就是「糊一两秒然后自然变清晰」比「卡死三秒然后突然全清」更让人受得了。

说到这儿必须插一句我自己踩过的坑。早年我坚信「FEC 开大就稳」,在一个项目里把冗余率拉到 35%,测试机在办公室千兆内网跑得飞起。上线后第一批用户里有一批用移动网络的,反馈是「越玩越卡」。查了半天才发现,冗余包把上行占满了,而我们那条链路的瓶颈不在下行——FEC 是在上行方向加的。这个错很低级,但它说明一件事:所有冗余策略都必须回答「这多出来的带宽从哪来」,如果答案是「从已经很紧张的那条管子里挤」,那这个策略就是负收益。

策略适用条件恢复代价带宽成本失效场景
选择性重传RTT < 半个帧间隔约 1 个 RTT低(只补关键包)高 RTT、长肥管道
FEC 前向纠错丢包以随机小簇为主0(本地算回来)10%~30% 冗余突发大簇丢包、带宽紧张
丢弃 + 掩盖这一帧已撞线等到下个关键帧0关键帧间隔过长时画面长期糊
请求整帧 IDR画面已彻底不可用1 个 RTT + 传输时间极高(5~10 倍瞬时)链路本就拥塞,会引发风暴

顺带说一句,这一层还有个容易被忽视的设计点:关键帧间隔(GOP)。GOP 太长,画面一旦坏掉,靠自然恢复要等很久,玩家体验是「持续的马赛克」;GOP 太短,码率浪费在反复的 I 帧上。云游戏里常见的折中是 1~2 秒一个关键帧,再配上一个「收到客户端请求就提前插一个」的快速通道。这套组合拳比死磕某一个参数管用得多。

第三层:拥塞控制和发包节奏,到底归谁管

前面两层讲的是「丢了怎么办」,这一层讲的是「怎么尽量别丢」。而这个问题的答案,在开放标准和私有协议之间分得最开。

为什么私有协议普遍自己写拥塞控制

WebRTC 里内置了一套拥塞控制(GCC,Google Congestion Control),它看两样东西:丢包率和延迟梯度。QUIC 里内置了 NewReno、CUBIC,还能选 BBR。这些东西都是好东西,都是十几年打磨出来的——但它们的设计目标里,有一条跟云游戏是冲突的:公平性。

通用协议的拥塞控制要保证「同一条链路上,多个流大家分得公平」。云游戏不在乎公平,它在乎的是「我这条流绝对不能抖」。当它跟一个正在疯狂下载的浏览器共享一条 20Mbps 的家用宽带时,GCC 会做出「礼貌」的让步,码率往下砍——而玩家感受到的就是画面突然糊成一片。

所以私有协议的选择往往是:自己写一套更激进、更「自私」的拥塞控制,宁可让邻居难受也要保住自己的帧。这在公共互联网上是个道德灰色地带,但坦白说,商业云游戏平台都这么干,只是程度不同。

pacing:把一帧的包铺开,别一口气倒出去

发包节奏(pacing)这个词,很多人第一次听到会觉得是吹毛求疵:包发出去不就完了?

不。假设你这一帧要发 100KB,链路可用带宽是 20Mbps(约 2.5MB/s),那么这 100KB 理论上需要 40ms 才能送完——已经超过了一帧的时间。如果你的程序一口气把这 100KB 全塞进 socket,它会瞬间把链路的队列填满,排在后面的包要等前面的发完,这个等待就叫「排队延迟」。更糟的是,排队会吃掉路由器和基站的缓冲区,缓冲区满了就开始丢包,于是出现「明明带宽够,却一直丢包」的怪事。

pacing 的做法是:按目标速率,把这一帧的包均匀地铺在时间轴上发出去。业内常见的做法是把链路利用率控制在 85% 到 95% 之间,留出 5% 到 15% 的空隙给突发流量和 ACK 回传,避免把队列顶到满。

这里有个工程细节特别值得说:你不能指望操作系统的 socket 帮你做这件事。内核的发送缓冲区、网卡的分段卸载(GSO)、中断合并,这一整套机制是为吞吐优化的,不是为延迟优化的。所以私有协议栈一般要绕开默认行为——把发送缓冲设得很小、自己控制每次写的字节数、必要时用原始套接字。这类操作在 WebRTC 里你基本做不了,因为那一层不是你的。

这也是「私有协议赢在控制权」最实在的一个注脚:它赢的不是算法更聪明,而是它能把手伸进操作系统和网卡的那几个旋钮里。

控制通道与媒体通道:必须物理分开

Parsec、Steam Link、GameStream 这三家,架构上有个惊人的一致性:控制走一条通道,媒体走另一条通道,而且两者的可靠性语义完全不同。

GameStream 的经典做法是 RTSP(可靠,跑在 TCP 上)管会话,RTP(不可靠,跑在 UDP 上)管视频。Steam Link 的思路类似,客户端会持续回报「我收到了第几帧」,服务端据此调整码率和帧率。Parsec 也是控制与媒体分离。

为什么非要分开?想象一下把玩家的输入塞进一条「保证送达、按序到达」的可靠队列里。网络一丢包,TCP 会重传,而重传期间后面所有的输入都被卡住——玩家推了一下摇杆,人物没反应;他松手了,人物才动。这种「输入延迟抖动」比画面延迟更让人抓狂,因为它直接破坏了「操作感」。玩家的手是极其敏感的传感器,20ms 的不一致他就能察觉。

所以输入必须走「要么到,要么丢,但绝不阻塞」的路径,而且必须带单调递增的序号防重放。会话控制(开始、结束、切分辨率)可以走可靠通道,因为它不频繁,慢一点无所谓。

WebRTC 里其实也能做这件事——DataChannel 支持无序不可靠模式。但它的实现要穿过 SCTP over DTLS 这一层,包头开销和状态机复杂度都不轻。而对云游戏这种「输入包又小又密」的场景,每一层封装都是纯开销。这就是为什么大家宁可自己写一套 UDP 上的轻量封装。

Steam Link:帧级反馈做成产品

Valve 的 Remote Play(也就是 Steam Link 背后的那套东西)在这件事上有个很「产品经理」的处理:它把「性能覆盖层」直接做成了玩家可见的功能——你可以实时看到帧率、码率、丢包、输入延迟、显示延迟这一串数字。

这个设计意图特别值得琢磨。它承认了一件事:云游戏的手感问题,是无法完全消灭的,只能被管理。与其让玩家在「为什么卡」的猜疑中流失,不如把仪表盘摊开,让他知道「哦是我家 Wi-Fi 的问题」。这跟第一层讲的帧级反馈是同一个哲学的另一面——把不可见的延迟,变成可见的数字,玩家对它的容忍度会显著上升。

第四层:加密和认证放在管线的哪一格

这一层最容易被当成「安全团队的事」甩出去,但它其实是个纯正的架构决策,直接影响延迟、可优化空间和运营成本。

云游戏的数据管线大致是:渲染 → 编码 → 分包 → 传输 → 收包 → 解码 → 显示。加密可以插在三个位置:编码前(内容加密)、传输层(整包加密)、分包后(仅加密载荷)。

开放标准的选择:DTLS-SRTP

WebRTC 走的是 DTLS-SRTP。会话建立时用 DTLS 握手协商密钥(1~2 个 RTT,支持会话恢复能省掉),之后每个媒体包用 SRTP 加密,包头带一个十几字节的认证标签。

这套方案的优点是成熟、有标准、跨实现互通。缺点是重:状态机复杂、握手有额外往返、每个包多十几个字节。对于 1080p60、码率 20Mbps 的场景,十几字节的开销摊到 1400 字节的包上大概是 1%,不算致命。真正的问题不在字节数,在于它把加密和传输死死绑在一起,你就没法在中间做任何手脚。

私有协议的选择:轻量 + 分层

私有协议的普遍做法是:用 AES-GCM 或 ChaCha20-Poly1305 这类现代 AEAD 算法,只加密载荷、保留明文头(或者只加密头里不影响路由的部分),每个包的认证标签压到最小。密钥在会话建立时协商一次,之后定期轮换。

这么做省下的不只是字节。保留明文头意味着中间节点可以做「无状态转发决策」——比如边缘节点看一眼帧序号就知道这帧过期了,可以直接丢,不用解密。这在多级转发架构里是实打实的性能差异。

一个被忽略的取舍:端到端加密 vs 边缘可优化

这才是加密位置真正的大问题。

如果你做端到端加密(只有发送端和接收端能解密),那么中间的所有边缘节点都只能当「哑管道」——它们不能转码、不能做 FEC 修复、不能针对最后一公里做码率适配。这对隐私是好事,对体验是坏事。

Google 当年做 Stadia 的时候,选择的是另一条路:边缘节点深度参与媒体处理。这不是因为它不在乎安全,而是因为云游戏的「最后一公里」太贵了——你的服务器在数据中心,玩家的手机在 4G 上,中间那段网络的质量你控制不了,唯一能控制的就是让边缘节点能实时调整。要能调整,就得能看见内容,或者至少能看见关键的头信息。

这个取舍没有标准答案,但它有个判断准则:你的边缘节点越多、离玩家越近,你就越需要放弃端到端加密。反过来,如果你全靠中心机房硬扛,端到端加密就是纯赚。

输入通道的防重放:一个真实的安全坑

最后提一个容易被漏掉的点。输入通道是作弊者的天堂——如果能录一段输入包重放,理论上可以复现别人的操作,或者在竞技游戏里做自动化。所以输入包必须带单调递增的序号和时间窗,服务端对超出窗口的包一律丢弃。

这个机制和「丢包就丢、不重传」的设计是天然契合的,因为丢掉旧包本来就是这个系统的默认行为。设计得好的话,安全性和延迟优化会互相成就,而不是互相打架——这种「顺手就做了」的设计,是我个人最喜欢的一类系统设计。

参数化设计工具:一张能直接抄回去改的配置表

下面这张表是我这些年攒下来的,覆盖了串流协议里最常调、也最容易调错的十个参数。建议区间是经验值,不是圣旨——你的用户网络结构不一样,最优解一定不一样。但「调错的症状」这一列是通用的,你可以拿它当排障手册用。

参数作用建议区间调错的典型症状
帧 deadline 余量一帧允许的迟到时长帧间隔的 50%~70%太小:频繁丢帧、画面抖;太大:手感黏
选择性重传窗口多久之内的包还值得补0.5~1.5 倍帧间隔太宽:重传包挤占带宽,恶性循环
FEC 冗余率纠错包的占比10%~30%,按实测丢包动态调定死 30%:弱网用户上行被冗余打爆
IDR 请求最小间隔防 IDR 风暴≥500ms,且全局限流不限流:一丢包就雪崩
关键帧间隔(GOP)画面自愈速度1~2 秒太长:坏帧之后长时间糊;太短:码率浪费
pacing 链路利用率发包节奏的目标占用85%~95%100%:排队延迟飙升,明明带宽够却卡
发送缓冲大小内核侧排队深度尽量小(单帧量级)默认值:延迟被缓冲区悄悄吃掉 30ms
jitter buffer抗抖动缓冲0~1 帧,跟随抖动方差固定 0:Wi-Fi 抖动下画面碎;固定 3 帧:手感废
码率爬升步长拥塞恢复速度每次≤10%,间隔≥200ms激进爬升:周期性拥塞、画面反复糊清
控制通道超时会话保活判断200~500ms太短:网络抖一下就被踢出房间

用这张表有一条心法:先定 deadline,再定其他一切。因为所有的可靠性策略、缓冲策略、编码策略,最终都是在回答同一个问题——「这一帧还来不来得及」。deadline 定错了,后面全错。

双路径对照:初级用户速览 / 进阶深入

初级用户速览(5 分钟读完)

如果你只是想搞懂「为什么有些云游戏手感好,有些不行」,记住五句话就够了:

  • 云游戏传的是帧,不是流。每一帧都有 deadline,迟到就作废,补发没意义。
  • 丢包时「扔掉」常常比「等」更快。因为等一个 RTT 就已经晚了两三帧。
  • FEC 是用带宽买时间。它不需要等往返,但会实打实多花 10%~30% 的流量。
  • 输入必须走单独通道。把操作塞进会重传的队伍里,玩家会立刻感觉到「手感断了」。
  • 私有协议赢在控制权,不是赢在算法。它能把 jitter buffer、发包节奏、重传窗口这些旋钮拧到极致,通用协议不让你碰。

还有一个反直觉的提醒:如果你自己在家搭了 Sunshine + Moonlight,发现延迟比某商业云平台低,先别急着下结论说「开源协议赢了」。很可能赢的是「你的电脑就在隔壁房间,RTT 是 1ms」,而商业平台的服务器在 300 公里外。这个变量能把协议优劣的差异完全淹没。

进阶深入(给要动手的人的清单)

如果你真要设计或改造一条云游戏传输通道,我建议按这个顺序做,每一步都能独立上线、独立验证:

  • 第一步,把帧序号和 deadline 打进去。这是地基,做完了你才有资格谈后面所有优化。验收标准:你能在客户端实时打印「这一帧迟到了多少毫秒」。
  • 第二步,做 pacing。把发送缓冲压小,自己控制每批写多少字节。验收标准:在网络测速工具显示带宽够的情况下,排队延迟不再随时间累积。
  • 第三步,做丢弃策略。迟到超过 deadline 的帧,直接不进解码队列。验收标准:高丢包下画面会变糊,但不会「卡住然后快进」。
  • 第四步,加 FEC。从 10% 起步,按实测丢包率动态调,别定死。验收标准:轻微丢包时画面不碎。
  • 第五步,才是拥塞控制。前面四步做完了,你会发现拥塞控制要解决的问题已经缩小了很多。

这个顺序背后的逻辑是:越靠前的步骤,收益越大、风险越小、越容易被回滚。反过来,一上来就死磕拥塞控制算法,是很多团队的经典弯路——因为他们的真正问题可能只是发送缓冲区设成了默认值。

争议与常见坑

坑一:「拿 WebRTC 改改就够了。」这句话听着省钱,实际上经常更贵。你得把 GCC 换掉、把 jitter buffer 拆掉、把 DataChannel 的语义改掉、还得跟 DTLS 握手较劲。Xbox Cloud Gaming 就是拿 WebRTC 大改的,工程量一点不比自研小。判断标准很简单:如果你需要改的东西超过三个核心组件,自研反而更清爽。

坑二:「FEC 开满就稳了。」FEC 只在「随机小簇丢包」下高效。遇到基站切换那种成片丢包,冗余包自己也会成片丢,一个都救不回来。而且它永久性地多花带宽,在移动网络上这笔账经常算不过来。

坑三:「重传一定比 FEC 好,因为不浪费带宽。」只在 RTT 足够小的时候成立。跨省 4G 的 RTT 经常 50ms 起,那时候重传就是纯浪费,FEC 才是正解。这两个不是替代关系,是按 RTT 分段的组合。

坑四:「加密会拖慢。」现代 CPU 上 AES 有硬件指令加速,加解密的开销在总延迟里通常排不进前三。真正的成本在架构——端到端加密会让边缘节点变成哑管道,你就失去了最后一公里的优化空间。这个代价比加密本身大得多。

坑五:只算协议账,不算部署账。这是最常见的误判。有人测出 Moonlight 比某商业平台快 20ms,就写文章说「私有协议碾压」。实际上那 20ms 里可能 15ms 来自「你的电脑在隔壁」、5ms 来自「你家宽带没有别人抢」。协议本身能贡献的部分,往往只有个位数毫秒。

还有一个更宏观的争议,我觉得值得摆到台面上:私有协议是不是在制造碎片化?

支持私有协议的一方说:游戏串流的需求太特殊,通用标准永远追不上。反对的一方说:Stadia 关掉之后,它那套协议也随之消失,用户和开发者的投入全打了水漂;而 WebRTC 活得好好的,因为它有整个互联网在维护。

我的看法偏中间但更靠近后者一点点:私有协议适合做「差异化」,不适合做「唯一方案」。一个健康的架构应该有一条开放标准的兜底通道(比如 WebRTC 用于兼容、用于企业网络、用于浏览器直连),再加一条私有协议做体验上限。全押私有协议的团队,最终都会在「维护成本」和「生态兼容」这两件事上付学费——我自己就付过。

FAQ

Q1:私有协议到底比 WebRTC 快多少?给个诚实数字。

在同一个机房、同一台机器、同样编码参数下对比,差距通常是「个位数毫秒」,极端配置下最多十几毫秒。很多人想象中的「快一倍」是不存在的。真实世界里你感受到的巨大差异,绝大部分来自部署位置(服务器离你多远)、网络路径质量(有没有被别人抢带宽)和硬件(是不是裸金属)。协议是那最后一层薄薄的加成,但它确实是「你唯一能完全掌控」的那一层。

Q2:我一个人做独立游戏串流,该选哪条路?

如果只是做原型验证,直接用现成的:Sunshine + Moonlight 这套开源组合,接上你自己的游戏,一天就能跑起来,别自己造。如果是要做产品,先问自己「我的用户主要在什么网络下玩」——局域网/同城为主,自研的收益很明显;广域网为主,先把边缘节点铺好再谈协议,因为位置比协议重要十倍。最不划算的选择是「广域网 + 自研协议 + 单中心机房」,三条都踩反。

Q3:帧级重传会不会把延迟拖爆?

会,如果你不设窗口的话。正确的做法是给每个重传请求算一次「还来不来得及」——用当前估计的 RTT 加上剩余 deadline 余量做判断,来不及就一个字节都不发。这个判断做得好,重传就是纯收益;做得不好,它就是雪崩的起点。顺带一提,这也是为什么你必须在协议栈里维护一个实时的 RTT 估计,而不是用 ping 出来的那个数。

Q4:Moonlight 逆向 NVIDIA 的协议,这事合法吗?

这是个灰色地带,而且各家司法辖区不一样。大致上的共识是:为了互操作性而进行逆向工程,在不少地区被认为是允许的,但如果你分发了受版权保护的代码或绕过了技术保护措施,性质就变了。Moonlight 的做法是「只重写协议行为,不复制原厂代码」,这是社区一直强调的边界。至于商业产品要不要走这条路,我的建议是:别赌。协议逆向可以作为学习和原型的手段,产品化请自己设计。

Q5:私有协议怎么过 NAT 和防火墙?企业网络里岂不是死路一条?

这是私有协议最现实的软肋。通用的解法是:先打 STUN 做穿透,穿不过就走 TURN 中继,同时准备一条「UDP 443 端口」的后备路径——因为大多数企业防火墙会放过 443,哪怕它是 UDP。再不行就退回 TCP 或者 WebSocket,牺牲延迟换连通率。这套降级链是必须做的,不做的话,你在任何一家公司内网里都可能连不上。顺带说,这也是「保留一条开放标准兜底通道」的另一个理由:WebRTC 的穿透能力是白送的。

最后抛个问题给你:如果未来某天,某个平台把「零延迟感知」做成了标配,你觉得那会是因为协议更聪明,还是因为边缘节点铺到了你家楼下?我押后者。协议的天花板其实很低,真正稀缺的从来都是「离玩家近」这件事。

文章标签
云游戏Cloud Gaming视频编码H.264H.265AV1WebRTC低延迟传输ABR 自适应码率GPU 虚拟化DLSS 云游戏FSR 云游戏
更多专题全部专题
觉得有价值?点赞或收藏支持内容持续产出。
← 返回专题:云游戏技术专题