构建 WebRTC 应用所需的后端服务(中文译文)

原文:Building the backend services required for a WebRTC application(web.dev)
链接:https://web.dev/articles/webrtc-infrastructure

本文系统阐述要搭建一个可用的 WebRTC 应用需要哪些后端能力与组件:信令服务、JSEP/SDP、ICE 候选收集与交换、STUN/TURN NAT 穿越、会话状态管理与安全;并以代码示例说明 Offer/Answer 与候选的传递与处理。文末附有架构与时序图,帮助读者快速把握整体流程。

在这里插入图片描述


目录

  • 信令是什么、为何 WebRTC 不规定信令
  • JSEP 架构与 SDP(Offer/Answer)
  • RTCPeerConnection 与候选(ICE)交换
  • 候选滴灌(Trickling)与建链加速
  • 如何编写/部署信令服务
  • STUN/TURN 与 NAT 穿越
  • 会话安全:DTLS 与 SRTP
  • 参考代码:完整信令流程(JS 摘要)
  • 部署参考与实践建议
  • 附:架构与时序图(重绘)

什么是信令

信令是用来“协调通信与建立会话”的过程。在 WebRTC 中,两个客户端在开始传输音视频前,需要通过某种信令机制交换如下信息:

  • 会话控制消息:创建/结束通话、错误提示。
  • 媒体元数据:编解码器类型与参数、带宽设置、媒体类型(音频/视频)。
  • 安全相关数据:建立安全连接所需的关键材料(例如 DTLS 指纹)。
  • 网络数据:主机对外可见的 IP/端口,以及各类可用的网络候选(ICE Candidates)。

重要的是:WebRTC 规范本身不定义“信令必须如何实现”。开发者需要自建或选用现有的信令方案(如 WebSocket/HTTP(S)/SSE 等),只要能可靠传递上述信息即可。


为什么 WebRTC 未定义信令

WebRTC 选择“不规定信令协议”的设计,是为了避免重复造轮子、增强与既有技术的兼容性。与此配套的是 JSEP(JavaScript Session Establishment Protocol)的理念:

  • 浏览器不保存信令状态;状态应由应用或服务端管理,这样即使刷新页面也不会导致会话流程信息丢失。
  • 应用通过“Offer/Answer + 候选交换”的通用模型完成连接建立,传输内容为 SDP 文本与候选对象,具体如何承载由应用自由选择(比如经 WebSocket 推送)。

JSEP 的核心是:让应用掌控信令流程,浏览器专注媒体/网络能力与实时传输。


SDP 与 Offer/Answer 简介

WebRTC 用 SDP(Session Description Protocol)表达会话配置。典型 SDP(精简示例)如下:

v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=rtpmap:111 opus/48000/2
...

应用可以在将 SDP 设置为本地或远程描述之前,对其中的文本进行修改(例如偏好某编解码器或调整比特率)。尽管直接在 JS 中操作 SDP 文本略显繁琐,但其通用性与生态兼容是目前方案的优势。


RTCPeerConnection 与信令:Offer、Answer 与 Candidate

RTCPeerConnection 是浏览器端用于在对等体之间建立连接与传输音视频的 API。完整流程如下(示例:Alice 呼叫 Eve):

  1. Alice 创建 RTCPeerConnection,使用 createOffer() 生成 SDP Offer;随后调用 setLocalDescription() 设置本地描述,并通过信令把 Offer 发送给 Eve。
  2. Eve 收到后调用 setRemoteDescription() 应用 Alice 的 Offer;再调用 createAnswer() 生成 SDP Answer;调用 setLocalDescription() 设置自己的本地描述,并通过信令把 Answer 发回 Alice。
  3. Alice 接收 Answer 后调用 setRemoteDescription() 完成会话协商。

除此之外,双方还需交换网络候选(ICE Candidates)。在 onicecandidate 回调里,浏览器会逐步产出候选,应用通过信令传递给对端;对端收到后调用 addIceCandidate() 添加,参与连通性检查。最终,ICE 会选择一对“能打通且符合策略”的候选作为媒体传输路径。


候选滴灌(ICE Candidate Trickling)

Trickling 指:在初始 Offer/Answer 之后,候选陆续产生并发送,不必等所有候选收集完毕才开始建链。这样可以显著缩短首包时间并提升建链成功率。


编写与部署信令服务

由于规范不限定信令实现方式,常见选择包括:

  • WebSocket:双向、低延迟、适合实时信令;
  • HTTP(S):简单、可复用现有后端,但需要长轮询或 SSE 才能近实时;
  • 其他:MQ、gRPC、SSE 等。

信令服务的通用能力:

  • 会话与房间管理、用户注册与鉴权;
  • Offer/Answer 与候选的转发与(可选)持久化;
  • 安全与审计:跨设备与跨网络访问控制、日志记录与监控;
  • 异常恢复:处理“抢占/碰撞”(glare)与重试。

STUN/TURN 与 NAT 穿越

为了让不同网络环境下的设备互通,需要借助 ICE 框架与 STUN/TURN:

  • STUN:向 STUN 服务器查询自身对外可见的地址(反射),提升直连成功率;
  • TURN:当直连不可达时,通过 TURN 中继媒体流;TURN 支持 UDP/TCP/TLS 多种承载。

常用策略:优先尝试直连(host/srflx);在受限网络或企业防火墙下回退到 TURN(relay),必要时使用 TURN/TLS(走 443),提高成功率但增加延迟与云端带宽成本。


会话安全:DTLS 与 SRTP

WebRTC 使用 DTLS 握手建立安全会话密钥;媒体层使用 SRTP/SRTCP 加密与保护。即使在 TURN/TLS 中继路径下,媒体仍以 SRTP 形式传输,只是底层承载由 UDP 切换为 TCP/TLS。


参考代码:完整信令流程(JS 摘要)

下述代码展示一个最小化的信令流程(假设存在 SignalingChannel,负责 JSON 序列化与消息交换):

// 假设封装了 JSON.stringify / JSON.parse
const signaling = new SignalingChannel();
const constraints = { audio: true, video: true };
const configuration = { iceServers: [{ urls: 'stun:stun.example.org' }] };
const pc = new RTCPeerConnection(configuration);

// 逐步将本地 ICE 候选发送给对端
pc.onicecandidate = ({ candidate }) => signaling.send({ candidate });

// 触发协商时生成并发送 Offer
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    signaling.send({ description: pc.localDescription });
  } catch (err) {
    console.error(err);
  }
};

// 处理来自信令通道的远端消息
signaling.onmessage = async ({ description, candidate }) => {
  try {
    if (description) {
      const readyForOffer = pc.signalingState === 'stable' || pc.signalingState === 'have-local-offer';
      const offerCollision = description.type === 'offer' && !readyForOffer; // 处理 glare 的一种思路

      if (offerCollision) {
        // 根据应用策略处理 Offer 碰撞
        return;
      }

      await pc.setRemoteDescription(description);
      if (description.type === 'offer') {
        await pc.setLocalDescription(await pc.createAnswer());
        signaling.send({ description: pc.localDescription });
      }
    } else if (candidate) {
      await pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
};

部署参考与实践建议

架构参考

  • 前端/客户端:浏览器或原生终端,采集设备并使用 RTCPeerConnection;
  • 信令服务:房间与会话状态管理、鉴权、Offer/Answer 与候选转发;
  • NAT 穿越:STUN/TURN(建议多区域部署,支持 UDP/TCP/TLS);
  • 运营与监控:日志、指标、容量与成本管理。

建议

  • 优先直连 UDP,启用带宽自适应(REMB/TWCC)与错误恢复(NACK/PLI),仅在不可达时回退 TURN/TCP/TLS;
  • 使用候选滴灌减少建链等待;在信令层持久化会话状态以提升健壮性;
  • 在必要时调整 SDP(编解码器与码率),但尽量以标准 API 与策略控制为主;
  • 做好安全与合规:鉴权、审计与日志不可或缺。

参考与致谢

  • 构建 WebRTC 应用所需的后端服务(web.dev 原文):https://web.dev/articles/webrtc-infrastructure
  • IETF/W3C 相关规范(SDP/JSEP/ICE 等)。

(本译文与示意图用于学习交流,建议结合原文与标准文档理解细节。)

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐