WebRTC 详解:无需服务器中转的浏览器间实时通信
打开一个视频通话应用时,音视频乃至聊天消息,往往并不经过服务器中转,而是直接在浏览器之间传输。实现这一切的就是 WebRTC(Web Real-Time Communication)。它把信令、NAT 穿透、加密、数据传输等多项技术捆绑在一起,把 P2P 通信带到了浏览器这个普及程度最高的平台之上。本文详细拆解这套机制的构造与取舍。
WebRTC 是什么
WebRTC 是一整套 API 与协议的统称,让浏览器与移动应用能够不经过服务器中转、直接交换音频、视频以及任意数据。Google 于 2011 年将其作为开源项目发布,此后标准化工作在两条战线同时推进:W3C 负责面向浏览器的 JavaScript API,IETF 负责底层协议,最终于 2021 年分别落地为 W3C 的 WebRTC 1.0 推荐标准,以及以 RFC 8825(概述)为代表的一系列 IETF RFC。在 WebRTC 之前,浏览器中的实时音视频通信要依赖 Flash、原生应用 SDK 或专用插件;WebRTC 让「打开一个浏览器标签页」就足以进行 P2P 实时通信。
WebRTC 的核心价值在于,把浏览器这一限制颇多的环境中原本需要各自实现的诸多环节(信令、NAT 穿透、加密、拥塞控制、媒体编解码)统一打包成一套标准 API。开发者只需操作 RTCPeerConnection 这一个对象,就无需直接面对其背后运行的 ICE、STUN/TURN、DTLS、SRTP、SCTP 等复杂协议栈的细节。
核心组成
- RTCPeerConnection
- WebRTC 的核心 API,代表与单个对端的一条连接。添加媒体轨道与数据通道、收集 ICE 候选、生成与应用 SDP offer/answer 等建立连接所需的几乎所有操作,都通过这一对象完成。
- MediaStream(getUserMedia)
- 通过
navigator.mediaDevices.getUserMedia()从摄像头、麦克风等设备获取的音视频流。将获取到的轨道添加进RTCPeerConnection,即可直接发送给对端。用于屏幕共享的getDisplayMedia()属于同一族 API。 - RTCDataChannel
- 用于传输媒体以外任意数据的双向通道。底层实现是 SCTP(Stream Control Transmission Protocol)跑在 DTLS 之上(SCTP over DTLS)。既支持类似 TCP 的可靠、有序模式,也可以通过
ordered、maxRetransmits等选项选择更接近 UDP 的低延迟、不保证顺序的模式。 - ICE(Interactive Connectivity Establishment)
- 用于在两个对端之间找出实际可通信路径的框架(RFC 8445)。双方各自收集若干候选路径并互相交换,再逐一进行连通性检测,最终选出可用且最优的一对候选路径。
- SDP(Session Description Protocol)
- 用文本格式(RFC 8866)描述即将建立的媒体会话:所用编解码器、媒体类型、加密参数、ICE 候选等。WebRTC 通过一轮「offer/answer」的 SDP 交换来就会话内容达成一致。
连接建立的过程
- 信令(signaling):两个对端交换 SDP offer/answer 与 ICE 候选的阶段。需要特别指出的是,WebRTC 规范本身并未规定信令的传输方式,只要 SDP 能够送达即可。因此实际实现中通常借助 WebSocket 服务器或其他消息服务来完成,也就是说,即便媒体与数据本身是 P2P 传输的,建立连接这一「牵线搭桥」的过程仍然离不开某种服务器(下一节将详细介绍信令的具体方式)。
- 收集 ICE 候选:各对端收集自己可能被对方触达的候选路径,大致分为三类:代表本地网络接口地址的 host 候选、通过向 STUN 服务器查询而得知的公网可见地址即 server reflexive 候选,以及当直接连通无法建立时用作中继点的 relay 候选(经由 TURN 服务器)。
- 通过 STUN/TURN 实现 NAT 穿透:STUN 服务器所做的仅仅是告诉客户端「你在外部看起来是什么样子」,配合UDP 打洞技术,在多数 NAT 环境下已足以打通一条直连路径。当对称型 NAT 等情况导致直连始终无法建立时,TURN 服务器就作为「最后手段」在双方之间转发数据包。
- DTLS 握手:确定实际可用路径后,双方在该路径上进行 DTLS(Datagram TLS)握手,完成密钥交换与双向身份认证。
- 媒体/数据的收发:在已建立的加密路径之上,音视频以 SRTP(Secure RTP)形式传输,任意数据以 SCTP 形式传输。此后的全部实际数据都在这条加密隧道中流动。
这里有一点值得强调:WebRTC 是 P2P,但并非「无服务器」。信令环节必须依赖某种服务器(或至少是一条既有的通信通道),而绝大多数实际部署也都会准备 STUN 服务器,并额外部署 TURN 服务器作为直连失败时的兜底。对称型 NAT 叠加严格防火墙等直连根本无法建立的情况在实践中并不罕见,此时经由 TURN 中继的通信,实质上与「经服务器转发」并无区别。WebRTC 提供的是「优先尝试直连、失败则回退到中继」的设计,而不是一套彻底消除服务器的机制。
信令详解
WebRTC 的协商基于 JSEP(JavaScript Session Establishment Protocol,RFC 8829)模型。规范唯一要求的,只是把 SDP offer/answer 与 ICE 候选送达对端,至于用什么方式传输,是 WebSocket、HTTP、电子邮件,甚至手动复制粘贴,规范都刻意留白。这种自由度带来了实现上的多样性,但「何时、以何种方式交换 ICE 候选」这一选择,会显著影响连接建立的速度与实现复杂度。
Non-Trickle ICE(Vanilla ICE)
最朴素的方式:调用 setLocalDescription() 后,先等待 ICE 候选收集完全结束,再一次性交换包含全部候选的完整 SDP。优点在于信令收敛为一次往返,一份 offer、一份 answer,因此即便是不具备实时性的通道(单次 HTTP 请求、二维码、手动复制粘贴)也能完成整个交换。缺点是候选收集过程包含枚举网络接口、STUN 查询、TURN 分配等步骤,一旦某个服务器无响应就可能因等待超时而耗费数秒,期间用户只能干等,因此不适合对连接建立体感速度敏感的场景。
Trickle ICE
Trickle ICE(RFC 8838)不等候选收集结束,而是先立即发送不含(或仅含少量)候选的 SDP,此后每发现一个新候选,就通过 onicecandidate 事件像「涓流(trickle)」一样逐个发送给对端;接收端则用 addIceCandidate() 逐个添加,候选一到即可立即开始 NAT 穿透 所需的连通性检查。这样一来,SDP 交换、候选收集、连通性检查三个环节得以并行推进,大幅缩短了连接建立所需的时间,浏览器的 WebRTC 实现也因此默认采用 Trickle ICE。其前提是需要一条能随时传递候选的双向低延迟信令通道(常见做法是使用 WebSocket)。两端都需要支持 trickle,不过兼容性可以平滑退化:对不支持 trickle 的对端,仍可按 Non-Trickle 的方式处理。
WHIP / WHEP:基于 HTTP 的标准化信令
信令自由的另一面是碎片化:各家服务自造协议,导致推流设备与软件之间几乎没有互操作性可言。IETF 对此给出的回应是 WHIP(WebRTC-HTTP Ingestion Protocol,RFC 9725):用一次 HTTP 往返完成 SDP 交换。它面向的是推流(ingest)场景:编码器(如 OBS)向媒体服务器「推送」媒体时,用 HTTP POST 发送 SDP offer,在响应中收到 answer 即完成协商。面向观众拉流(egress)的配套规范是 WHEP(WebRTC-HTTP Egress Protocol),目前仍在标准化推进之中。其动机是用具备亚秒级延迟的 WebRTC 取代长期由 RTMP 承担的直播推流角色。基本流程本质上就是 Non-Trickle 的一次往返,但也以可选项形式规定了通过 HTTP PATCH 实现 Trickle ICE 与 ICE restart。随着 OBS Studio 以及主流 CDN、媒体服务器陆续跟进支持,WHIP 正在确立其「WebRTC 时代的 RTMP」的地位。
| 维度 | Non-Trickle(Vanilla) | Trickle ICE | WHIP/WHEP |
|---|---|---|---|
| SDP 交换形态/次数 | 一次往返,SDP 已包含全部候选 | 多轮:先发不含候选的 SDP,候选随后逐个补发 | 一次 HTTP 往返(可选 PATCH 追加 trickle) |
| 候选发送方式 | 收集完毕后随 SDP 一次性发送 | 通过 onicecandidate 逐个「涓流」式发送 | 默认随初始 SDP 一次性发送,可选 HTTP PATCH 追加 |
| 连接建立速度 | 较慢,需等待候选收集全部结束 | 较快,交换与收集并行推进 | 与 Non-Trickle 相当,追加 trickle 后可提升 |
| 对信令通道的要求 | 只需能传达一次消息(HTTP、二维码、复制粘贴均可) | 需要双向、低延迟的实时通道(如 WebSocket) | 仅需支持 HTTP POST/PATCH,无需长连接 |
| 典型用途 | 无实时信令通道的简单场景 | 浏览器间标准 WebRTC 通话/会议 | OBS 等编码器向媒体服务器推流、CDN 拉流 |
网状信令:迈向无服务器信令
P2P 网络一旦长成规模,已经建立好的 P2P 连接本身,就足以为新连接的加入承载信令:已经入网的节点充当中间人,经由自己与新加入者之间既有的数据通道,转发对方与网络其他成员交换 SDP/ICE 所需的消息。随着越来越多的信令流量改走这条路径,专用信令服务器承担的负载随之下降,网络对信令服务器故障的韧性也相应提高。
但鸡与蛋的约束依然存在:一个尚无任何连接的全新参与者,其「第一条连接」终究还是要依赖某种外部引导,比如服务器、中继,或是某条已经存在的通信通道。网状信令只有在网络已经存在之后才谈得上用武之地,它能省去的是「后续每一个新节点」的信令成本,而不是「从零开始」这一步本身。
更进一步的变体,是用 Nostr 这类通用的 pub/sub 中继取代专用信令服务器,作为传递信令消息的载体。这种做法下有一套惯用的隐私设计:签署信令消息所用的,是与应用层持久身份完全解耦的一次性临时密钥,真正的身份信息只存在于已加密的载荷内部,这样一来,就连中继的运营者自己,也无从得知究竟是谁在与谁建立连接。
安全性
WebRTC 中,媒体与数据通道都强制加密,规范本身根本不提供「不加密」这个选项。DTLS 负责协商会话密钥,此后音视频始终以 SRTP 形式、数据通道始终以 DTLS 承载的 SCTP 形式收发,未加密的传输方式不存在。
- IP 地址泄露的隐患:在 ICE 候选收集过程中,浏览器会生成暴露本地私网 IP 地址的候选,以及通过 STUN 查询而暴露公网 IP 地址的候选。即便用户正在使用 VPN,这套候选生成机制也可能把真实 IP 地址暴露出去,这就是曾广受关注的「WebRTC IP 泄露」问题。
- 通过 mDNS 候选来缓解:目前主流浏览器已默认启用相应缓解措施:不再直接公开本地 host 候选的原始 IP 地址,而是用一个随机生成的
.localmDNS 主机名替代后再共享。这样既能保证同一局域网内的连接正常建立,又能避免向无关第三方泄露真实的私网 IP 地址。
拓扑结构的局限:从全网状到 SFU/MCU
对于一对一通话,两个对端直接建立连接的全网状(full mesh)结构完全没有问题。但当参会人数达到 n 人、所有人都试图维持全网状连接时,每个对端都要同时维护 n-1 条连接,并持续向其余所有人上传自己的音视频。连接数会以 O(n²) 的速度增长,每个客户端的上行带宽与处理能力很快就会成为瓶颈,这套方案只适用于人数较少的通话场景。
面对这一局限,现实中的解法是重新引入中心化的服务器角色,即 SFU(Selective Forwarding Unit,选择性转发单元) 或 MCU(Multipoint Control Unit,多点控制单元)。SFU 本质上是一台路由器:它接收每个参会者的媒体流,不做解码直接转发给其他参会者,这样每个客户端只需承担一路上传,加上相当于 (n-1) 路的下载。MCU 则更进一步,在服务器端把所有人的音视频混合、合成为单一流后再下发,从而降低客户端的负担,但也大幅提高了服务器端的计算成本。主流视频会议服务大多采用 SFU 方案。这本质上是「P2P 理想」与「面向多人的现实可扩展性」之间的取舍,与 P2P 与元宇宙 中讨论的多人同时连接问题一脉相承。
应用场景
- 视频会议与语音通话:Google Meet、Discord、Zoom 浏览器版等诸多视频会议服务,都以 WebRTC 作为媒体传输的基础。
- 文件传输:借助 RTCDataChannel,浏览器之间可以直接互传文件,无需先上传到服务器再转发。
- 浏览器中的 P2P 网络:RTCDataChannel 并非只服务于媒体传输,它同样是一条可承载任意二进制数据的通用 P2P 管道。基于这条管道,WebTorrent 让浏览器能够直接加入类似 BitTorrent 的分享群组(swarm);浏览器版 IPFS 节点以及 libp2p 的 WebRTC 传输层,也让浏览器无需安装专用应用,就能成为 P2P 网络中名副其实的参与者。这意味着此前只能从服务器下载的浏览器,如今也能作为对等节点承担上传的角色,这一转变颇具意义。
对比:WebSocket、WebTransport、WebRTC DataChannel
| 维度 | WebSocket | WebTransport | WebRTC DataChannel |
|---|---|---|---|
| 连接形态 | 客户端-服务器(始终为一对一,必须有服务器) | 客户端-服务器(始终为一对一,必须有服务器) | 端到端(服务器仅协助信令与 NAT 穿透) |
| 传输层 | TCP | QUIC(基于 UDP) | SCTP over DTLS(基于 UDP) |
| 可靠性与顺序性 | 固定为 TCP 语义:始终可靠、始终保序 | 按流可选;数据报(datagram)不保证可靠 | 可按流通过 ordered、maxRetransmits 等选项灵活选择 |
| 是否需要信令 | 不需要:直接连接 URL 即可 | 不需要:直接连接 URL 即可 | 需要:必须预先交换 SDP/ICE 候选 |
| 主要用途 | 通用的客户端-服务器实时双向通信(聊天、通知等) | 低延迟的服务器通信、媒体流、多路数据报传输 | 视频会议的音视频/聊天、P2P 文件传输、浏览器 P2P 网络 |
相关页面
WebRTC 得以尝试直连的根基,正是 NAT 穿透 中介绍的 STUN/TURN/ICE 机制;信令环节则通常借助 WebSocket 实现。多人连接时暴露出的可扩展性问题,与 P2P 与元宇宙 中讨论的并发连接数问题一脉相承;SFU/MCU 这类拓扑取舍在一对多的直播场景中演变为分发树与级联,详见 P2P 直播。而基于 RTCDataChannel 构建的浏览器 P2P 网络(如 WebTorrent),则直接继承了 BitTorrent 的分享群组机制。如果只是需要与服务器进行简单的双向通信,WebRTC 的复杂度其实并无必要,这种场景可参考更轻量的 WebSocket 与 WebTransport。