基础概念

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
// 用户 A 创建 Offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

// ❌ 错误:不能直接发送
// webSocket.send(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) {
// 1️⃣ 认证用户
User sender = authService.getCurrentUser();
if (sender == null) {
throw new UnauthorizedException("未登录");
}

// 2️⃣ 验证目标用户存在且在线
User receiver = userService.findById(req.getTargetUserId());
if (receiver == null || !receiver.isOnline()) {
throw new BadRequestException("用户不存在或离线");
}

// 3️⃣ 检查权限(是否被屏蔽等)
if (blockService.isBlocked(sender.getId(), receiver.getId())) {
throw new ForbiddenException("该用户已将你屏蔽");
}

// 4️⃣ 转发 Offer 给对方(通过 WebSocket)
WebSocketMessage msg = new WebSocketMessage()
.setType("offer-received")
.setFrom(sender.getId())
.setData(req.getOffer());

webSocketService.sendToUser(receiver.getId(), msg);

// 5️⃣ 记录日志
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();

// 转发 Answer
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
// 监听来自服务器的 Answer
webSocket.onmessage = (event) => {
const msg = JSON.parse(event.data);

if (msg.type === 'answer-received') {
// 收到对方的 Answer
const remoteDescription = new RTCSessionDescription(msg.data);
await peerConnection.setRemoteDescription(remoteDescription);

// 此时媒体通道开始建立
console.log('Answer received, connection negotiating...');
}
};

成本对比

传统流媒体(服务器转发所有数据)

1
2
3
4
参与人数    服务器带宽消耗
11 1x 基准
1N (N-1)x 基准 × 通话人数
成本呈指数增长

WebRTC(只转发信令)

1
2
3
4
参与人数    服务器带宽消耗
11 0.1x 基准(只有信令)
1 对 N 0.1x 基准(几乎不增加)
成本基本保持不变

关键结论

✅ WebRTC 大幅降低了服务器的数据处理负担
❌ WebRTC 不能消除对服务器的需求
⚠️ 服务器角色从”媒体中转者“变为”控制协调者

主要应用场景

场景 示例
📹 视频会议 Zoom、Google Meet、Teams
💬 实时通讯 语音通话、文字聊天
🎮 多人游戏 低延迟的实时交互
📺 直播 低延迟的媒体流传输
🔄 文件共享 点对点文件传输
📊 屏幕共享 协作工具中的屏幕演示
🤖 IoT 通信 设备间的实时数据交互

WebRTC 的三大技术支柱

1. MediaStream - 获取媒体

ℹ️ 信息: 作用
获取本地摄像头、麦克风等设备的媒体流

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. 实时数据流动 │
│ ↓ │
│ 音频/视频/数据 → 对端浏览器 │
└─────────────────────────────────────────────────────────┘

详细步骤说明

  1. 媒体采集 - 获取本地摄像头和麦克风
  2. 连接创建 - 实例化 RTCPeerConnection
  3. 能力协商 - 主动方生成 Offer,被动方生成 Answer(描述支持的编码格式等)
  4. 地址发现 - ICE 收集所有可用的网络地址组合
  5. 连接建立 - 双方尝试可用地址,建立最优路径
  6. 数据传输 - 通过直连通道实时传输媒体

核心概念解释

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/防火墙后
  • 无法直接被外网访问
  • 需要找到能互通的地址组合

工作方式

  1. 收集所有可能的候选地址(本地 IP、公网 IP 等)
  2. 通过 STUN/TURN 服务器辅助地址发现
  3. 双方交换候选地址
  4. 尝试建立连接
  5. 使用第一条成功的路径

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/媒体编码 - 了解媒体传输细节

基础概念
https://zmmmmy.github.io/2026/01/21/基础概念/
作者
ZhiMy
发布于
2026年1月21日
许可协议