你有没有想过,当你和朋友视频通话时,视频流是怎么从你的摄像头传到对方屏幕的?在 WebRTC 出现之前,实时音视频通信依赖服务器中转,延迟高、带宽成本大。而 WebRTC(Web Real-Time Communication) 让浏览器可以直接建立点对点(P2P)连接,无需插件、无需服务器中转媒体流,彻底改变了 Web 实时通信的格局。
本文将从原理到代码,带你深入理解 WebRTC P2P 的完整技术体系。
一、什么是 WebRTC?
WebRTC 是由 Google 主导、W3C 和 IETF 标准化的开放技术规范,允许浏览器和移动应用通过简单的 JavaScript API 实现:
- 🎥 音视频通话(点对点传输,低延迟)
- 📂 任意数据传输(DataChannel,可传文件、消息)
- 🖥️ 屏幕共享
- 🎮 实时游戏数据同步
它的核心优势在于 P2P:两端直接通信,媒体数据不经过服务器,带宽消耗小、延迟极低(通常 < 100ms)。
二、WebRTC 的核心架构
┌─────────────────────────────────────────────┐ │ WebRTC 架构 │ │ │ │ Browser A Signaling Server │ │ ┌────────┐ SDP/ICE ┌──────────┐ │ │ │ Peer │◄────────────►│ Signal │ │ │ │ A │ │ Server │ │ │ └────┬───┘ └──────────┘ │ │ │ ▲ │ │ │ P2P Media/Data │ │ │ │◄──────────────────────►│ │ │ ┌────┴───┐ ┌──────────┐ │ │ │ Peer │◄────────────►│ Signal │ │ │ │ B │ SDP/ICE │ Server │ │ │ └────────┘ └──────────┘ │ │ Browser B │ └─────────────────────────────────────────────┘
|
WebRTC 的连接建立分为两个阶段:
- 信令阶段:通过信令服务器交换 SDP 和 ICE 候选(服务器仅做”牵线搭桥”)
- P2P 阶段:连接建立后,媒体流和数据直接在两端之间传输
三、关键协议栈
理解 WebRTC,必须了解其底层依赖的几个核心协议:
3.1 ICE(Interactive Connectivity Establishment)
ICE 解决的核心问题是:两台设备如何找到彼此并建立连接?
现实网络中,大多数设备都在 NAT(网络地址转换)后面,没有公网 IP。ICE 通过收集多种”候选地址”来尝试打洞:
| 候选类型 | 说明 |
|---|
| Host | 本机局域网 IP,直连最快 |
| Server Reflexive | 经 STUN 服务器获取的公网 IP |
| Relay | 经 TURN 服务器中继(最后备选) |
ICE 会对所有候选对进行连通性检测,选出最优路径。
3.2 STUN(Session Traversal Utilities for NAT)
STUN 服务器帮助客户端发现自己的公网 IP 和端口,成本极低(仅用于地址发现,不中转数据)。
Client → STUN Server: "我的公网 IP 是什么?" STUN Server → Client: "你的公网 IP 是 203.0.113.5:54321"
|
3.3 TURN(Traversal Using Relays around NAT)
当 P2P 直连失败(例如对称型 NAT),TURN 服务器作为中继转发所有数据。代价是带宽成本增加,但保证了连通性。
3.4 SDP(Session Description Protocol)
SDP 是一种文本格式,用于描述媒体会话的参数,包括:
- 支持的音视频编解码器(VP8、H.264、Opus 等)
- 媒体方向(发送/接收/双向)
- ICE 候选地址
- 加密参数(DTLS 证书指纹)
v=0 o=- 46117317 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE audio video m=audio 9 UDP/TLS/RTP/SAVPF 111 a=rtpmap:111 opus/48000/2 a=sendrecv
|
四、P2P 连接建立流程(信令过程)
WebRTC 连接的建立遵循严格的”提议-应答”(Offer-Answer)模型:
Peer A Signaling Server Peer B │ │ │ │── getUserMedia() ────────│ │ │ │ │ │── createOffer() ────────►│ │ │ (生成 SDP Offer) │── 转发 Offer ───────────►│ │ │ │── createAnswer() │ │◄── 转发 Answer ───────────│ (生成 SDP Answer) │◄── setRemoteDescription()│ │ │ │ │ │── 收集 ICE 候选 ──────────│── 转发 ICE candidates ──►│ │◄──────────────────────── │── 转发 ICE candidates ───│ │ │ │ │◄════════ P2P 连接建立,直接通信 ════════════════════►│
|
六步建立连接:
- 获取本地媒体流(摄像头/麦克风)
- Peer A 创建
RTCPeerConnection,调用 createOffer() 生成 SDP
- Peer A 通过信令服务器将 Offer 发送给 Peer B
- Peer B 收到 Offer,调用
createAnswer() 生成应答 SDP
- 双方交换 ICE 候选地址
- ICE 连通性检测完成,P2P 连接建立 ✅
五、核心 API 代码实战
5.1 获取本地媒体流
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 }, audio: true });
const localVideo = document.getElementById('localVideo'); localVideo.srcObject = stream;
|
5.2 创建 RTCPeerConnection
const configuration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'turn:your-turn-server.com:3478', username: 'user', credential: 'password' } ] };
const peerConnection = new RTCPeerConnection(configuration);
stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); });
peerConnection.ontrack = (event) => { const remoteVideo = document.getElementById('remoteVideo'); remoteVideo.srcObject = event.streams[0]; };
|
5.3 发起方:创建 Offer
async function createOffer() { peerConnection.onicecandidate = (event) => { if (event.candidate) { signalingServer.send({ type: 'ice-candidate', candidate: event.candidate }); } };
const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer);
signalingServer.send({ type: 'offer', sdp: offer }); }
|
5.4 接收方:处理 Offer 并回复 Answer
async function handleOffer(offer) { peerConnection.onicecandidate = (event) => { if (event.candidate) { signalingServer.send({ type: 'ice-candidate', candidate: event.candidate }); } };
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer);
signalingServer.send({ type: 'answer', sdp: answer }); }
|
5.5 添加 ICE 候选
async function handleIceCandidate(candidate) { await peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); }
|
5.6 DataChannel:传输任意数据
const dataChannel = peerConnection.createDataChannel('chat', { ordered: true });
dataChannel.onopen = () => console.log('DataChannel 已开启'); dataChannel.onmessage = (e) => console.log('收到消息:', e.data);
dataChannel.send('Hello, P2P!'); dataChannel.send(JSON.stringify({ type: 'file', name: 'photo.jpg' }));
peerConnection.ondatachannel = (event) => { const receiveChannel = event.channel; receiveChannel.onmessage = (e) => { console.log('接收到:', e.data); }; };
|
六、完整信令服务器示例(Node.js + WebSocket)
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 });
const rooms = new Map();
wss.on('connection', (ws) => { ws.on('message', (message) => { const data = JSON.parse(message);
switch (data.type) { case 'join': if (!rooms.has(data.roomId)) { rooms.set(data.roomId, []); } rooms.get(data.roomId).push(ws); ws.roomId = data.roomId; break;
case 'offer': case 'answer': case 'ice-candidate': const room = rooms.get(ws.roomId) || []; room.forEach(peer => { if (peer !== ws && peer.readyState === WebSocket.OPEN) { peer.send(JSON.stringify(data)); } }); break; } });
ws.on('close', () => { const room = rooms.get(ws.roomId); if (room) { const index = room.indexOf(ws); if (index > -1) room.splice(index, 1); } }); });
console.log('信令服务器运行在 ws://localhost:8080');
|
七、NAT 穿透:P2P 连接的最大挑战
现实中并非所有 P2P 连接都能成功建立,关键在于 NAT 类型:
NAT 类型 P2P 成功率 ─────────────────────────── Full Cone ✅ 高(最容易穿透) Restricted Cone ✅ 较高 Port Restricted ⚠️ 中等 Symmetric NAT ❌ 低(需要 TURN 中继)
|
实际统计:约 85% 的连接可以通过 ICE 直接建立 P2P,剩余 15% 需要 TURN 中继。因此生产环境必须部署 TURN 服务器作为兜底。
推荐开源 TURN 服务器:coturn
apt-get install coturn
listening-port=3478 fingerprint lt-cred-mech user=webrtc:yourpassword realm=yourdomain.com
|
八、安全性
WebRTC 在设计上强制要求加密,所有传输默认安全:
| 层级 | 协议 | 说明 |
|---|
| 媒体传输 | SRTP | 音视频数据加密 |
| 数据传输 | DTLS | DataChannel 数据加密 |
| 密钥协商 | DTLS-SRTP | 密钥在 P2P 连接中直接协商 |
⚠️ 注意:信令服务器本身不在 WebRTC 规范内,需要开发者自行保证信令通道的安全(使用 WSS/HTTPS)。
九、常见应用场景
| 场景 | 技术点 |
|---|
| 视频会议 | getUserMedia + 多路 P2P 或 SFU 架构 |
| P2P 文件传输 | DataChannel + ArrayBuffer 分片 |
| 在线游戏 | DataChannel(unreliable 模式,低延迟) |
| 远程桌面 | getDisplayMedia + 视频轨道 |
| 实时字幕 | DataChannel 传输 STT 结果 |
多人会议架构选择
2人通话: A ←──P2P──→ B (纯 P2P,最优)
3-4人会议: A ←─ P2P ─→ B (Mesh,每人需多路连接) ↕ ↕ C ←─ P2P ─→ D
大规模会议: 所有人 ──→ SFU 服务器 ──→ 分发 (推荐,如 mediasoup)
|
十、调试技巧
Chrome 提供了内置的 WebRTC 调试工具:
# 在浏览器地址栏打开 chrome://webrtc-internals
# 可以查看: # - ICE 连接状态和候选地址 # - SDP 协商内容 # - 实时音视频统计(码率、丢包率、延迟) # - DataChannel 状态
|
peerConnection.onconnectionstatechange = () => { console.log('连接状态:', peerConnection.connectionState); };
peerConnection.oniceconnectionstatechange = () => { console.log('ICE 状态:', peerConnection.iceConnectionState); };
const stats = await peerConnection.getStats(); stats.forEach(report => { if (report.type === 'inbound-rtp' && report.mediaType === 'video') { console.log('视频丢包率:', report.packetsLost / report.packetsReceived); console.log('帧率:', report.framesPerSecond); } });
|
总结
WebRTC P2P 技术的精妙之处在于:
- 🔗 信令与媒体分离:服务器只负责”牵线”,数据直接在端之间流动
- 🧩 协议协同:ICE + STUN + TURN 三者配合,解决复杂网络环境下的连通性
- 🔐 安全内置:SRTP + DTLS 强制加密,无需额外配置
- 🌐 浏览器原生支持:无需插件,现代浏览器开箱即用
WebRTC 已经成为实时通信领域不可绕过的基础技术。无论是构建视频会议、文件传输还是实时游戏,深入理解其 P2P 连接原理都能让你在系统设计时做出更好的决策。
参考资料