19、WebRTC入门:WebRTC架构、信令与NAT穿透、P2P连接建立
WebRTC,全称是Web Real-Time Communication。说白了,就是让浏览器之间能直接传音视频数据,不需要经过服务器中转。我最早接触WebRTC是在2015年,当时公司要做一款在线教育产品,要求低延迟互动。嗯,那时候WebRTC还没完全标准化,踩了不少坑。但今天回头看,它已经是实时音视频领域的事实标准了。
19.1 WebRTC整体架构
WebRTC的架构,我习惯把它分成三层来看:
- API层:暴露给开发者的JavaScript接口,比如
getUserMedia、RTCPeerConnection、RTCDataChannel。 - 引擎层:包括音频引擎、视频引擎、网络传输引擎。这部分是C++实现的,负责编解码、降噪、丢包重传等脏活累活。
- 底层:包括音频采集/渲染、视频采集/渲染、网络I/O。这部分直接跟硬件和操作系统打交道。
你想想看,为什么WebRTC能火?因为它把最复杂的部分——音视频编解码、网络抖动缓冲、拥塞控制——都封装好了。开发者只需要调用几个API,就能实现点对点通话。我在项目中见过太多团队试图自己造轮子,最后都老老实实回来用WebRTC。
核心要点:WebRTC不是单一协议,而是一整套解决方案。它包含了音视频采集、编解码、网络传输、NAT穿透等所有环节。
19.2 信令:建立连接前的握手
很多人第一次接触WebRTC时都会问:不是说P2P吗?为什么还需要服务器?
原因很简单:两个浏览器之间要建立连接,总得先知道对方在哪吧?信令就是干这个的。它负责交换三个关键信息:
- 会话描述(SDP):包含音视频编解码能力、IP地址、端口等信息。
- 网络候选(ICE Candidate):告诉对方自己有哪些可用的网络路径。
- 密钥信息:用于DTLS加密协商。
WebRTC规范并没有规定信令怎么传。你可以用WebSocket、HTTP轮询、甚至用鸽子送信(开玩笑的)。我在项目中一般用WebSocket,因为它双向实时,延迟低。
个人经验:信令服务器虽然简单,但千万别掉以轻心。我曾经遇到过信令丢失导致连接失败的情况——客户端发了offer,但answer没收到。后来加了ACK确认机制才解决。
19.3 NAT穿透:为什么需要STUN/TURN
现实世界中的设备,大部分都在NAT后面。你想想看,家里的路由器、公司的防火墙,都是NAT。它们把内网IP映射成公网IP,但同时也阻止了外部主动发起的连接。
WebRTC解决这个问题的方案是ICE框架,它包含三种候选类型:
| 候选类型 | 来源 | 成功率 | 说明 |
|---|---|---|---|
| Host | 本机网卡 | 低 | 只有局域网内可用 |
| Srflx | STUN服务器 | 中 | 能穿透大部分NAT |
| Relay | TURN服务器 | 高 | 所有流量经服务器中转 |
STUN的原理很简单:客户端向STUN服务器发请求,服务器返回客户端的公网IP和端口。这样客户端就知道自己在公网上的地址了。
但STUN不是万能的。有些对称NAT,STUN也穿透不了。这时候就得用TURN——所有音视频数据都经过服务器中转。说白了,TURN是最后的保底方案。
避坑指南:我曾经在一个项目中只部署了STUN,没部署TURN。结果有20%的用户连不上——他们都在对称NAT后面。后来加了TURN服务器,连接率才到99%以上。记住:生产环境一定要部署TURN。
19.4 P2P连接建立:ICE流程详解
P2P连接的建立,我习惯把它分成四个步骤:
- 收集候选:双方各自收集Host、Srflx、Relay候选。
- 交换候选:通过信令通道交换候选列表。
- 连通性检查:双方互相发送STUN binding请求,测试哪对候选能连通。
- 选最优路径:从所有连通的候选对中,选择优先级最高的。
ICE的连通性检查很有意思。它会按照候选对的优先级排序,从高到低依次尝试。一旦找到能连通的,就停止检查。这个过程通常只需要几百毫秒。
// 伪代码:ICE连通性检查流程
function iceConnectivityCheck(localCandidates, remoteCandidates) {
// 1. 生成候选对
let pairs = [];
for (let local of localCandidates) {
for (let remote of remoteCandidates) {
pairs.push({ local, remote, priority: computePriority(local, remote) });
}
}
// 2. 按优先级排序
pairs.sort((a, b) => b.priority - a.priority);
// 3. 依次检查
for (let pair of pairs) {
let result = sendStunBinding(pair.local, pair.remote);
if (result.success) {
return pair; // 找到可用路径
}
}
return null; // 所有路径都失败
}
关键点:ICE的优先级计算有一套复杂的公式。简单来说,Relay候选优先级最低,Host候选优先级最高。因为直连肯定比中转快。
19.5 完整的连接建立时序
把信令、NAT穿透、ICE流程串起来,完整的P2P连接建立是这样的:
嗯,这里要注意:步骤⑤的ICE连通性检查,是直接在客户端A和客户端B之间进行的,不经过信令服务器。这也是为什么它叫P2P——信令服务器只负责牵线搭桥,真正的数据流是直连的。
我的建议:调试WebRTC连接问题时,一定要看ICE状态变化。Chrome的chrome://webrtc-internals页面非常有用,能看到所有候选、连通性检查结果、选中的路径。我排查问题时,90%的线索都来自这里。
19.6 实际项目中的注意事项
最后分享几个我在项目中踩过的坑:
- STUN/TURN服务器要就近部署:如果用户在欧洲,服务器在美国,延迟会很高。最好用云服务商提供的全球节点。
- ICE重启:网络切换时(比如WiFi切4G),ICE会重新协商。这时候要处理好状态迁移,否则会断连。
- 信令超时:信令交换必须在合理时间内完成。我一般设置30秒超时,超时后提示用户检查网络。
- 编解码协商:不是所有浏览器都支持H264。我习惯优先用VP8,兼容性最好。
WebRTC入门其实不难,难的是把各种边界情况处理好。但只要你理解了信令、NAT穿透、ICE这三个核心概念,剩下的就是经验积累了。
公众号:蓝海资料掘金营,微信deep3321