WebRTC 是什么
WebRTC(Web Real-Time Communication)是一组开源的浏览器 API 与协议标准,允许在不需要任何插件的情况下,直接在浏览器之间建立点对点(Peer-to-Peer)的实时音视频通信和数据传输。2011年由 Google 开源,现已成为 W3C 推荐标准,被 Chrome、Firefox、Safari、Edge 等主流浏览器广泛支持。
与传统的 C/S 架构不同,WebRTC 的核心设计理念是“媒体流直接在浏览器之间传输”,服务器只负责最初的信令协商,真正的音视频数据不经过中转。这不仅降低了服务器带宽成本,还将端到端延迟控制在毫秒级别,为视频会议、在线教育、远程协作和实时直播等场景提供了原生能力。
核心 API
WebRTC 在浏览器端暴露了三组核心接口,分别承担采集、连接和数据传输的职责。
getUserMedia 用于获取本地媒体流。通过指定 { audio: true, video: true } 等约束条件,调用后会触发浏览器权限弹窗,授权后返回包含音轨和视频轨的 MediaStream 对象。
RTCPeerConnection 是整个 WebRTC 的枢纽。它负责管理 ICE 候选收集、SDP 协商、编解码器协商以及网络传输,内部自动处理 NAT 穿透、拥塞控制和带宽自适应。
RTCDataChannel 提供在已有 PeerConnection 之上构建任意数据通道的能力,基于 SCTP over DTLS,支持可靠或不可靠传输,可用于传输JSON、文件或二进制游戏状态数据。
信令过程:为什么需要它
WebRTC 是“去中心化”的通信,但两台浏览器在初次通信时互不知道对方的存在,也没有对方的网络地址和媒体能力。因此必须引入一个**信令服务器(Signaling Server)**来完成初次“握手”。
信令过程采用经典的“提议—应答”模型:一方生成 Offer 描述自己的媒体能力(编解码、ICE候选等),通过信令服务器转发给另一方;另一方收到后生成 Answer 回传。至此,双方均持有对端的会话描述,才能开始真正的媒体协商。
值得注意的是,WebRTC 规范刻意不规定信令层的具体协议。开发者可以自由选择 WebSocket、Socket.IO、SIP、XMPP 甚至轮询 HTTP 来实现,只要保证 Offer/Answer 和 ICE 候选能够可靠交换即可。
ICE 框架与候选收集
ICE(Interactive Connectivity Establishment)是 WebRTC 解决网络层连接的核心框架。它的目标是在复杂的 NAT 和防火墙环境中,找到一条可达的传输路径。ICE 收集三类候选地址:
- Host 候选:本机直接拥有的 IP 与端口,例如
192.168.1.10:54321。同一内网的两台设备可直接连通。 - Server Reflexive(srflx)候选:通过 STUN 服务器获取的本机公网映射地址。STUN 服务器告诉客户端“你的公网 IP 是
203.0.113.5:12345”,对端可尝试直接向该地址发包。 - Relay 候选:通过 TURN 服务器中继的地址。当所有直连尝试失败时,媒体流会经过 TURN 服务器转发,牺牲部分延迟换取连通性。
ICE 会按优先级并行测试各候选路径,通常 Host > srflx > Relay,成功连通后即刻停止搜索。
SDP:会话描述协议
SDP(Session Description Protocol)是一种基于文本的格式,用于描述一次多媒体会话的“元信息”。虽然名字叫协议,本质上是一个结构化文本块,不涉及传输逻辑。
一份 WebRTC 的 SDP 至少包含三个关键信息:媒体类型(音频/视频/数据)、编解码器列表(如 VP8、VP9、H.264、Opus)以及 ICE 候选和 DTLS 指纹。例如 m=audio 9 UDP/TLS/RTP/SAVPF 111 表示使用端口 9、在 DTLS 加密之上的 RTP 协议、优先级 111 的负载类型。
在 setLocalDescription 和 setRemoteDescription 两个 API 调用后,浏览器内部的 SDP 解析器会提取上述信息,驱动后续的 ICE 和 DTLS 握手。
NAT 穿越与对称型NAT困境
家庭路由器和企业防火墙普遍部署 NAT(Network Address Translation),将内网私有地址映射到公网地址。NAT 分为多种类型:全锥型、地址限制型、端口限制型以及对称型 NAT。
对称型 NAT 的问题在于:它针对每一个不同的目的地址分配不同的公网映射端口。当客户端向 STUN 服务器查询公网地址后,对端尝试向该地址发包,由于源地址不同,NAT 会重新分配端口,导致连通失败。这是 STUN 无法穿透的场景,也是 TURN 服务器必须存在的原因——当 P2P 直连不可行时,TURN 作为中继保证通信不中断。
生产环境中,Google 的公共 STUN 服务器(stun.l.google.com:19302)适合测试,但商业应用应部署私有 STUN/TURN 服务(如 coturn)以保障稳定性和隐私。
DataChannel:不止于音视频
RTCDataChannel 常被忽视,却是 WebRTC 最具扩展性的特性。它复用了与媒体流相同的 DTLS 加密通道,但在之上跑的是 SCTP 协议,兼具 TCP 的可靠性和 UDP 的低延迟特征。
创建时可配置 ordered(是否保序)和 maxRetransmits(最大重传次数),实现从“类似 TCP”的可靠传输到“类似 UDP”的极速传输的灵活切换。典型应用场景包括:实时游戏状态同步、文件点对点直传、共享画板协作数据同步等。
安全性设计
WebRTC 从协议层面禁止任何形式的明文媒体传输。ICE 连通后,浏览器自动执行 DTLS(Datagram Transport Layer Security)握手,在 UDP 之上建立加密隧道。音视频 RTP 包以及 SCTP 数据均在 DTLS 内传输,等同于 UDP 版的 TLS。此外,浏览器要求信令中的媒体获取必须通过 HTTPS 页面触发,getUserMedia 在 insecure context 中直接拒绝,防止中间人劫持摄像头和麦克风。
完整示例:WebSocket 信令服务器与客户端
以下展示一套可直接运行的最小 WebRTC 示例。服务端使用 Node.js + ws 库,客户端为纯 HTML/JS。
server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Map();
wss.on('connection', (ws) => {
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
const target = clients.get(msg.to);
if (target && target.readyState === WebSocket.OPEN) {
target.send(JSON.stringify({ from: msg.from, type: msg.type, payload: msg.payload }));
}
});
ws.on('close', () => {
for (const [id, socket] of clients) {
if (socket === ws) clients.delete(id);
}
});
});
console.log('Signaling server running on ws://localhost:8080');
client.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebRTC Demo</title>
</head>
<body>
<video id="local" autoplay muted playsinline style="width:320px"></video>
<video id="remote" autoplay playsinline style="width:320px"></video>
<button id="start">Start Call</button>
<script>
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
const ws = new WebSocket('ws://localhost:8080');
const myId = Math.random().toString(36).slice(2);
let remoteId = null;
ws.onopen = () => ws.send(JSON.stringify({ type: 'register', id: myId }));
ws.onmessage = async ({ data }) => {
const msg = JSON.parse(data);
if (msg.type === 'offer') {
await pc.setRemoteDescription(new RTCSessionDescription(msg.payload));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
ws.send(JSON.stringify({ from: myId, to: msg.from, type: 'answer', payload: answer }));
}
if (msg.type === 'answer') {
await pc.setRemoteDescription(new RTCSessionDescription(msg.payload));
}
if (msg.type === 'ice') {
await pc.addIceCandidate(new RTCIceCandidate(msg.payload));
}
};
pc.onicecandidate = (e) => {
if (e.candidate && remoteId) {
ws.send(JSON.stringify({ from: myId, to: remoteId, type: 'ice', payload: e.candidate }));
}
};
pc.ontrack = (e) => {
document.getElementById('remote').srcObject = e.streams[0];
};
document.getElementById('start').onclick = async () => {
remoteId = prompt('Enter remote peer ID');
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
document.getElementById('local').srcObject = stream;
stream.getTracks().forEach(t => pc.addTrack(t, stream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({ from: myId, to: remoteId, type: 'offer', payload: offer }));
};
</script>
</body>
</html>
运行服务端后,在两个标签页中分别打开 client.html。A 点击 “Start Call” 并输入 B 的随机 ID,B 页面的控制台会打印 ID。B 点击按钮输入 A 的 ID,即可完成双向音视频互通。整个过程完整展示了 Offer/Answer 交换、ICE 候选收集及远程流渲染。
小结
WebRTC 将实时通信能力下沉到浏览器内核,开发者无需关注 codec 协商、NAT 穿透和加密握手的底层细节,只需理解信令交换流程并正确配置 STUN/TURN 服务。结合 DataChannel,WebRTC 早已超越音视频通话的范畴,成为浏览器端 P2P 实时系统的通用基础设施。在 5G 和低延迟直播需求日益增长的背景下,掌握 WebRTC 是现代前端工程师不可或缺的技术纵深。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。