技术博客

WebSocket 与 WebRTC 后端:浏览器平台的传输选择

第 118 / 149 篇 · 2026-06-15 · godot · 网络同步Web 导出WebRTC

把 Godot 游戏导出到浏览器,多人联机立刻矮半头:浏览器给不了裸 UDP socket,ENet 直接出局。剩下的选项是 WebSocket 和 WebRTC——一个是 TCP 上的消息流,一个是浏览器内建的 P2P 数据通道,底层特性天差地别。Godot 把两者都封装成了 MultiplayerPeer(WebSocketMultiplayerPeer、WebRTCMultiplayerPeer),上层多人代码一行不改,但传输层的差异会原样穿透到你的游戏体验里。这篇讲清差异在哪、怎么选。

WebSocket:简单,但背着 TCP 的原罪

WebSocket 是 HTTP 升级出来的双向消息流,浏览器原生支持,Godot 的 Web 导出里开箱即用。它继承了 TCP 的一切:可靠、有序、字节流(WebSocket 层切成消息帧)。

可靠有序在弱网下的代价就是七-4 讲过的队头阻塞,搬到浏览器里更严重——因为浏览器 WebSocket 跑在主线程事件循环上,抖动被放大。粗略建模:丢包率 $p$、RTT $R$ 的链路,TCP 见到一次丢包要等重传超时,期望 stall 时长

$$ E[T_{stall}] \approx R \cdot \frac{p}{1 - p} $$

$p = 2\%$、$R = 150$ms 时约 3ms 每包,看似不大,但 stall 是全序的:一次 stall 卡住的是整条流上排它后面的所有消息。位置同步恰好最怕这个——一个旧位置的可靠重传,把最新的位置更新堵在后面,画面整体冻结一拍。Web 上玩射击游戏觉得"粘滞",多半不是带宽不够,是这个。

但 WebSocket 的工程成本几乎为零:WebSocketPeer/WebSocketMultiplayerPeer 连 URL、开 TLS(wss://)、完事。服务器侧随便一个语言都有成熟库。对回合制、卡牌、低频同步游戏,它就是对的选择——这类游戏的流量语义本来就是"必须可靠有序"。

WebRTC:浏览器里唯一能碰 UDP 的门

WebRTC 的 DataChannel 是浏览器唯一暴露的不可靠/半可靠 UDP 数据通道,带 DTLS 加密。特性对游戏极其合身:可以选择 unordered、可以选择重传上限——位置同步走 unreliable+unordered,事件走 reliable,正好复刻 ENet 的传输模式模型。

代价在两个地方。第一是连接拓扑:WebRTC 设计目标是 P2P,两个浏览器之间建立连接前要先交换 SDP(会话描述)和 ICE candidate(网络路径候选,含 NAT 打洞用的公网地址)。这个交换不在 WebRTC 协议里——协议只告诉你"需要一个信令通道"。Godot 不提供信令服务器,你得自己写(通常就是一个 WebSocket 房间服务:进房、广播 SDP、转发 candidate)。第二个代价是ICE 本身可能失败:对称 NAT 下打洞失败的概率不可忽视,生产环境要配 STUN(公网地址发现)甚至 TURN(中继转发,带宽成本翻服务器上)。服务器权威架构下有个常用简化:服务器跑原生 Godot(用 ENet),只有客户端在浏览器里——浏览器客户端到服务器这段用 WebRTC 连,服务器侧有公网 IP 不需要打洞,P2P 复杂度消失,只剩 DataChannel 的传输特性红利。

还有个坑:Web 导出外的平台要跑 WebRTC,Godot 官方二进制不含WebRTC 模块——桌面/移动端要用社区 GDExtension 插件(webrtc-native)。混合平台项目要么全员装插件,要么桌面走 ENet、浏览器走 WebRTC,传输层按平台条件编译切换。

Web 平台传输选型:WebSocket vs WebRTC 数据通道 WebSocket(TCP 消息流) 可靠 + 全序,浏览器原生,服务器生态成熟 弱网队头阻塞:一次丢包 stall 整条流 E[T_stall] ≈ R·p/(1−p),对位置同步是硬伤 适合:回合制 / 卡牌 / 低频同步 工程成本:连 URL 即用,wss:// 自带 TLS WebRTC DataChannel(UDP) 可配 unreliable/unordered —— 传输模式模型全保留 DTLS 内建加密;浏览器唯一 UDP 入口 要自己写信令服务器交换 SDP/ICE NAT 打洞可能失败:需 STUN,生产加 TURN 适合:动作 / 高频同步;服务器权威可绕开 P2P 推荐拓扑:服务器权威 + 浏览器客户端 浏览器客户端 ──WebRTC──▶ 公网服务器(原生 Godot + ENet/WebRTC 均可) 服务器有公网 IP:SDP/ICE 直连成功,P2P 打洞复杂度消失 桌面/移动客户端同一服务器:ENet 直连,三种客户端一个逻辑层 注意:官方导出模板桌面端无 WebRTC,要 GDExtension 插件(webrtc-native)

选型决策

按游戏类型给结论:

  • 回合制 / 卡牌 / 策略:WebSocket。流量语义匹配,零额外基础设施,TLS 还顺手解决了浏览器"安全上下文"要求;
  • 动作 / 竞速 / 高频同步:WebRTC(DataChannel),但按上面说的服务器权威拓扑部署,别让浏览器之间 P2P;
  • 聊天、大厅、信令本身:WebSocket——它就是干这个的,正好兼任 WebRTC 的信令通道。

混合部署的工程细节

  • SharedArrayBuffer:Web 导出的线程(WorkerThreadPool 在浏览器里的替代)要求跨域隔离头(COOP/COEP),托管 Web 版时服务器要配 Cross-Origin-Opener-Policy: same-origin 和 Cross-Origin-Embedder-Policy: require-corp。Godot 4 的 Web 导出默认推荐开线程,漏配这两个头游戏直接黑屏,控制台报错信息不显眼——排查 Web 黑屏先看这两个头;
  • wss:// 强制:混合内容策略下,HTTPS 页面连 ws://(非加密)会被浏览器拦截。生产一律 wss://,本地调试 ws:// 没问题;
  • 多 WebRTC 连接的性能:一个浏览器开几十条 DataChannel 没问题,但每条都是独立 DTLS 会话,握手成本(几百 ms)在进房时集中爆发——进房流程做成异步逐个建连,别同步 await 全部;
  • Godot 侧代码:WebSocketMultiplayerPeer 和 WebRTCMultiplayerPeer 都实现 MultiplayerPeer,你的游戏逻辑只认 multiplayer.multiplayer_peer,按 OS.has_feature("web") 二选一即可,这段是项目里少数值得用单例封装的地方。

本文基于 Godot 4.2–4.4 的 Web 导出行为;WebRTC 插件指 webrtc-native GDExtension。ENet 在浏览器不可用的根本原因是 Web 平台无裸 UDP socket API。