WebRTC 基础概念
什么是 WebRTC?
WebRTC(Web Real-Time Communication) 是一项开源技术,允许 Web 浏览器和移动应用进行实时通信,无需中介服务器或插件。
核心定义
- Web - 在浏览器中运行,基于 JavaScript API
- Real-Time - 低延迟通信(通常在毫秒级)
- Communication - 支持音频、视频和任意数据传输
为什么需要 WebRTC?
传统方案的问题
| 问题 |
影响 |
| 浏览器无法直接访问摄像头/麦克风 |
需要第三方插件(Flash、ActiveX等) |
| 跨域通信困难 |
必须通过服务器中转 |
| 服务器中转 |
增加延迟、增加成本、难以扩展 |
| 信息安全 |
所有数据都经过服务器 |
WebRTC 的优势
✅ 原生浏览器 API - 无需插件,所有现代浏览器支持
✅ 点对点直连 - 降低延迟和服务器成本
✅ 内置 NAT 穿透 - 自动处理防火墙和内网问题
✅ 端到端加密 - 数据安全性高
✅ 跨平台 - Chrome、Firefox、Safari、Edge 等
WebRTC 架构中的服务器角色
⚠️ 警告: 重要澄清
WebRTC 不需要服务器转发媒体数据,但仍然需要服务器处理信令和控制
架构对比
传统项目(如 Java 后端视频服务)
1 2 3 4 5
| 浏览器A → 服务器 ← 浏览器B ↓ 所有数据都转发 (音频、视频、控制都经过服务器) 成本高,延迟大,扩展性差
|
WebRTC 项目
1 2 3 4 5
| 浏览器A ←→ 信令服务器 ←→ 浏览器B ↓ ↓ └──────────直连通道──────────┘ (仅媒体数据点对点) 成本低,延迟小,扩展性好
|
服务器需要处理的内容
| 内容 |
是否需要服务器 |
说明 |
| 音视频数据 |
❌ 不需要 |
直接点对点传输 |
| Offer/Answer |
✅ 需要 |
连接协商信息需要中介转发 |
| ICE 候选 |
✅ 需要 |
网络地址信息需要交换 |
| 用户认证 |
✅ 需要 |
验证用户身份 |
| 在线状态 |
✅ 需要 |
维护用户列表、房间状态 |
| 权限控制 |
✅ 需要 |
谁可以和谁通话 |
| 日志记录 |
✅ 需要 |
通话记录、问题诊断 |
| 录制/转码 |
⚠️ 可选 |
需要时才接收并处理媒体 |
完整的 WebRTC 项目架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| ┌──────────────────────────────────┐ │ 前端应用(浏览器) │ │ - HTML/CSS/JavaScript │ │ - WebRTC API 调用 │ │ - 用户界面 │ └──────────────────────────────────┘ ↕ WebSocket/HTTP ┌──────────────────────────────────────────────────────┐ │ 后端服务器(Java/Node.js/Go/Python等) │ │ │ │ 核心模块: │ │ ┌────────────────────────────────────────┐ │ │ │ 1. 用户管理 │ │ │ │ - 用户注册、登录、认证 │ │ │ │ - 在线状态维护 │ │ │ │ - 好友列表管理 │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ 2. 信令控制 │ │ │ │ - Offer/Answer 转发 │ │ │ │ - ICE 候选交换 │ │ │ │ - 连接状态管理 │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ 3. 房间/会议管理 │ │ │ │ - 创建/加入/离开房间 │ │ │ │ - 参与者管理 │ │ │ │ - 权限控制(谁能说话、共享等) │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ 4. 业务逻辑 │ │ │ │ - 计费/统计 │ │ │ │ - 内容审核 │ │ │ │ - 异常处理、心跳检测 │ │ │ └────────────────────────────────────────┘ │ │ │ │ 可选模块: │ │ ┌────────────────────────────────────────┐ │ │ │ 5. 媒体处理 │ │ │ │ - 录制媒体流 │ │ │ │ - 转码处理 │ │ │ │ - AI 分析(人脸识别等) │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ 6. NAT 穿透辅助 │ │ │ │ - STUN 服务器 │ │ │ │ - TURN 中继服务器 │ │ │ └────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ ↕ 仅在 NAT 穿透失败时 ┌──────────────────────────────────┐ │ TURN 中继服务器(可选) │ │ - 当直连失败时中继媒体数据 │ │ - 带宽成本较高 │ └──────────────────────────────────┘
|
实际代码示例
前端:发送 Offer(需要经过服务器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer);
fetch('/api/signaling/send-offer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ targetUserId: 'user-b', offer: offer }) });
|
后端:处理 Offer(Java 示例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @PostMapping("/api/signaling/send-offer") public void sendOffer(@RequestBody SignalingRequest req) { User sender = authService.getCurrentUser(); if (sender == null) { throw new UnauthorizedException("未登录"); }
User receiver = userService.findById(req.getTargetUserId()); if (receiver == null || !receiver.isOnline()) { throw new BadRequestException("用户不存在或离线"); }
if (blockService.isBlocked(sender.getId(), receiver.getId())) { throw new ForbiddenException("该用户已将你屏蔽"); }
WebSocketMessage msg = new WebSocketMessage() .setType("offer-received") .setFrom(sender.getId()) .setData(req.getOffer());
webSocketService.sendToUser(receiver.getId(), msg);
loggingService.log("User " + sender.getId() + " sent offer to " + receiver.getId()); }
|
后端:接收 Answer 并转发
1 2 3 4 5 6 7 8 9 10 11 12
| @PostMapping("/api/signaling/send-answer") public void sendAnswer(@RequestBody SignalingRequest req) { User sender = authService.getCurrentUser();
WebSocketMessage msg = new WebSocketMessage() .setType("answer-received") .setFrom(sender.getId()) .setData(req.getAnswer());
webSocketService.sendToUser(req.getTargetUserId(), msg); }
|
前端:接收 Answer 并建立连接
1 2 3 4 5 6 7 8 9 10 11 12 13
| webSocket.onmessage = (event) => { const msg = JSON.parse(event.data);
if (msg.type === 'answer-received') { const remoteDescription = new RTCSessionDescription(msg.data); await peerConnection.setRemoteDescription(remoteDescription);
console.log('Answer received, connection negotiating...'); } };
|
成本对比
传统流媒体(服务器转发所有数据)
1 2 3 4
| 参与人数 服务器带宽消耗 1 对 1 1x 基准 1 对 N (N-1)x 基准 × 通话人数 成本呈指数增长
|
WebRTC(只转发信令)
1 2 3 4
| 参与人数 服务器带宽消耗 1 对 1 0.1x 基准(只有信令) 1 对 N 0.1x 基准(几乎不增加) 成本基本保持不变
|
关键结论
✅ WebRTC 大幅降低了服务器的数据处理负担
❌ WebRTC 不能消除对服务器的需求
⚠️ 服务器角色从”媒体中转者“变为”控制协调者“
主要应用场景
| 场景 |
示例 |
| 📹 视频会议 |
Zoom、Google Meet、Teams |
| 💬 实时通讯 |
语音通话、文字聊天 |
| 🎮 多人游戏 |
低延迟的实时交互 |
| 📺 直播 |
低延迟的媒体流传输 |
| 🔄 文件共享 |
点对点文件传输 |
| 📊 屏幕共享 |
协作工具中的屏幕演示 |
| 🤖 IoT 通信 |
设备间的实时数据交互 |
WebRTC 的三大技术支柱
ℹ️ 信息: 作用
获取本地摄像头、麦克风等设备的媒体流
1 2 3 4 5 6 7 8
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
videoElement.srcObject = stream;
|
2. RTCPeerConnection - 建立连接
ℹ️ 信息: 作用
在两个浏览器间建立点对点的媒体连接
1 2 3 4 5 6 7 8 9
| const peerConnection = new RTCPeerConnection({ iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }] });
stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); });
|
3. RTCDataChannel - 传输数据
ℹ️ 信息: 作用
建立数据通道,传输任意类型数据(不仅仅是媒体)
1 2 3 4 5 6 7 8 9 10
| const dataChannel = peerConnection.createDataChannel('chat');
dataChannel.send('Hello, WebRTC!');
dataChannel.onmessage = (event) => { console.log('Received:', event.data); };
|
WebRTC 的工作流程
简化流程图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| ┌─────────────────────────────────────────────────────────┐ │ 1. 获取媒体流 │ │ ↓ │ │ navigator.mediaDevices.getUserMedia() │ │ (获取摄像头/麦克风) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 2. 创建对等连接 │ │ ↓ │ │ new RTCPeerConnection() │ │ (建立连接对象,配置 ICE 服务器) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 3. 交换会话信息(通过信令服务器) │ │ ↓ │ │ Offer (主动方) ↔ Answer (被动方) │ │ (SDP 协议描述媒体能力) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 4. 交换 ICE 候选 │ │ ↓ │ │ peerConnection.onicecandidate │ │ (发现网络地址,寻找可通路径) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 5. 建立直连通道 │ │ ↓ │ │ 连接状态: new → checking → connected → completed │ │ (使用 RTP/SRTP 传输媒体) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 6. 实时数据流动 │ │ ↓ │ │ 音频/视频/数据 → 对端浏览器 │ └─────────────────────────────────────────────────────────┘
|
详细步骤说明
- 媒体采集 - 获取本地摄像头和麦克风
- 连接创建 - 实例化 RTCPeerConnection
- 能力协商 - 主动方生成 Offer,被动方生成 Answer(描述支持的编码格式等)
- 地址发现 - ICE 收集所有可用的网络地址组合
- 连接建立 - 双方尝试可用地址,建立最优路径
- 数据传输 - 通过直连通道实时传输媒体
核心概念解释
SDP(会话描述协议)
📝 注意: 定义
SDP 是描述多媒体会话的协议,用于双方交换媒体能力信息
包含信息:
- 支持的媒体类型(音频、视频)
- 支持的编码格式(H.264、VP8 等)
- 支持的码率、分辨率等参数
- 网络传输信息(端口、协议等)
示例:
1 2 3 4 5 6 7 8 9
| v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.com s=Session SDP c=IN IP4 host.atlanta.com t=0 0 m=audio 49172 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 49170 RTP/AVP 31 a=rtpmap:31 H261/90000
|
ICE(交互式连接建立)
📝 注意: 定义
ICE 是一种协议技术,用于发现通信双方之间可用的网络路径
解决的问题:
- 浏览器位于 NAT/防火墙后
- 无法直接被外网访问
- 需要找到能互通的地址组合
工作方式:
- 收集所有可能的候选地址(本地 IP、公网 IP 等)
- 通过 STUN/TURN 服务器辅助地址发现
- 双方交换候选地址
- 尝试建立连接
- 使用第一条成功的路径
NAT 穿透
example: 示例场景
问题:局域网内的设备想与互联网上的设备通信
1 2 3
| 内网设备 192.168.1.100 → NAT 设备 → 互联网 ↑ 无法被外界直接访问
|
解决方案:
- STUN - 帮助设备发现自己的公网 IP
- TURN - 当直连失败时,作为中继服务器转发数据
传输协议栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌────────────────────────────────────────┐ │ 应用层 - JavaScript WebRTC API │ ├────────────────────────────────────────┤ │ 编码层 - VP8/VP9/H264(视频) │ │ - Opus/PCM(音频) │ ├────────────────────────────────────────┤ │ 传输层 - RTP(媒体数据) │ │ - SRTP(加密媒体) │ │ - SCTP(数据通道) │ ├────────────────────────────────────────┤ │ 安全层 - DTLS(加密握手) │ │ - SRTP(媒体加密) │ ├────────────────────────────────────────┤ │ 网络层 - UDP(不可靠但低延迟) │ ├────────────────────────────────────────┤ │ 物理层 - 网络传输 │ └────────────────────────────────────────┘
|
关键特点:
- 基于 UDP,而非 TCP(优先速度而非可靠性)
- 使用 SRTP 加密媒体数据
- 使用 DTLS 加密密钥交换
- 支持自适应比特率
关键优势总结
| 方面 |
优势 |
| 延迟 |
毫秒级,远低于服务器中转 |
| 成本 |
减少服务器带宽压力 |
| 安全 |
端到端加密,数据不过服务器 |
| 可用性 |
自动穿越 NAT 和防火墙 |
| 标准化 |
开放标准,所有浏览器统一实现 |
| 易用性 |
JavaScript API 简洁易用 |
局限性
⚠️ 信令服务器仍需要 - 虽然媒体点对点,但控制信息仍需服务器中转(通常用 WebSocket)
⚠️ TURN 服务器成本 - NAT 穿透失败时需要 TURN 中继,产生额外成本
⚠️ 浏览器兼容性 - 某些旧浏览器不支持
⚠️ 学习曲线 - 概念众多(SDP、ICE、STUN/TURN),初学者需要理解
下一步学习
- MediaStream/RTCPeerConnection/RTCDataChannel API - 深入 API 使用
- 信令服务器实现 - 学习如何交换连接信息
- ICE/STUN/TURN - 理解网络穿透原理
- RTP/SRTP/媒体编码 - 了解媒体传输细节