0x00 前言
WebRTC 中数据传输都是通过被称为 PeerConnection 的对象来完成的,PeerConnection 在可以传输数据前的建立过程相对于传统的 C/S 模式有略微差别,类似于 P2P 连接的建立过程,并且复用了传统的 STUN/TURN/ICE 架构的 P2P 实现方式。由于 WebRTC 支持 MESH/SFU/MCU 三种模式,使用 PeerConnection 概念的好处是可以同时兼容这三种模式,即使是像 SFU/MCU 这种非 P2P 的场景也同样使用 PeerConnection 来建立传输通道,不过针对 SFU/MCU 场景已经有一些优化方案,例如 ice-lite 。本文介绍了 P2P 和 SFU 场景中 PeerConnection 建立的过程。
0x01 WebRTC PeerConnection 建立过程
常规的 WebRTC PeerConnection 通信过程中有四种角色:
- Signaling Server
- STUN/TURN/ICE Server
- Local Peer
- Remote Peer
通信过程中的基本概念:
- Room: Signaling Server 使用 Room 的概念对一组通信的 peers 进行管理,一个 Room 中包含 一个或多个 peers,当没有 peers 存在时 Room 自动注销;当第一个 peer 连接到 Signaling Server 时执行 Create Room 操作;
- Offer/Answer:描述 peer 媒体属性的 SDP 信息通过 Offer/Answer 模式,由 Signaling Server 进行交换,通信建立前两个互相通信的 peer 需要互相获得对方的 媒体 SDP 信息;
- IceCandidate: Peer 与 STUN/TURN/ICE Server 通信,获取自己的 NAT 类型以及公网IP和端口,这些消息简称为 IceCandidate。和 媒体描述信息一样,IceCandidate 信息也需要互相通信的 peer 在通信前进行交换,在获取到对方的 iceCandidate 信息后双方通过 Stun binding 信令确认信道的联通性;
下图为经典的 WebRTC 中 PeerConnection 连接建立流程图:
通过上面的流程图可以总结出一下几点:
- peerConnnection 建立之前必须交换两个信息:SDP 和 iceCandidate;
- Answer 端接收到 Offer 后才会创建 PeerConnection 对象;
- Offer 端在创建成功本地 Offer 后才会去收集本地 iceCandidate;Answer 端在创建成功本地 Answer 后才会去收集本地 iceCandidate;
- peerConnection 两端都收到 iceCandidate 后才完成 peerConnection 的建立。
最佳实践
Webrtc Samples → Basic peer connection demo: https://webrtc.github.io/samples/src/content/peerconnection/pc1/
对应源文件:https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/pc1
通过浏览器的 JavaScript 接口了解 peerConnection 建立过程是一个非常不错的实践方式,上面的 sample 通过最简单的方式实现了两个 peerConneciton 的建立过程。
由于是在同一个 js 文件内创建两个 peerConnection,所有使用全局变量方式可以省略 Stun Server 和 Signal Server;
使用 Web API 建立简单 peerConnection 的大概流程为:
- 创建 peerConnection 对象 pc = new RTCPeerConnection(null);当初始化参数中 iceServers 为空时只能创建本地连接;
- 设置peerConnection 对象 事件响应操作
- pc.addEventListener(‘icecandidate’, xxx) // icecandidate 通知,WebRTC 默认开启了 trickle-ice,所以每探测到一个 iceCandidate 都会触发一次该事件,直到事件返回为 null 时,表示完成 iceCandidate 探测;
- pc.addEventListener(‘iceconnectionstatechange’, xxx) // ice 状态改变
- pc.addEventListener(‘track’, xxx) // 接收到 track
- 执行 peerConnection 操作
- pc.addTrack() 为 peerConnection 增加媒体流;
- pc.createOffer() / pc.createAnswer() 创建 SDP Offer / SDP Answer
- pc.setLocalDescription() Offer 端创建 Offer 后,Answer 端创建 Answer 后,将本地生成的 SDP 信息加入到本地 peerConnection;注意:iceCandidate 的收集过程发生在这个操作之后;
- pc.setRemoteDescription() Answer 端接收到 Offer 后,Offer 端接收到 Answer 后,将对端的 SDP 信息加入到本地 peerConnection;
- pc.addIceCandidate() 将对端生成的 iceCandidate 加入到本地 peerConneciton 的 ICE 代理中;
上面这个示例中使用到了 trickle-ice 协议,在 WebRTC 的 SDP 中通常可以包含三类信息
- ICE Parameters: candidate/ice-ufrag/ice-pwd
- DTLS Parameters: fingerprint/setup
- Media Parameters: extmap/rtpmap/fmtp/rtcp-fb/ssrc
也就是说 iceCandidate 信息是可以放到 SDP 里面的,省去了一次信息交换的过程,但是 iceCandidate 的收集过程是比较耗时的,所以 WebRTC 里面启用了 trickle-ice,上面示例中 createOffer 产生的 SDP 里面并没有 iceCandidate 信息,而是在 peerConnection 执行 setLocalDescription 操作后才开始收集 iceCandidate,收集到一个便触发一次 icecandidate 事件,然后通过信令通道输出到对端 peerConnection,对端 peerConneciton 通过执行 addIceCandidate 操作将对端的 iceCandidate 添加到本地 peerConnection 的 ICE agent 中。
为了验证将 iceCandidate 放到 SDP 中也可以实现 peerConnection 的连接,我们将上面的示例做了调整。当本地 iceCandidate 收集完成后再 createOffer,这样 SDP 信息中就携带了 candidate 信息, 修改后的示例请参考:
https://aggresss.github.io/webrtc-samples/src/content/peerconnection/pc1-mod/
0x02 SFU 模式 PeerConnection 建立过程
SFU 模式下,SFU 服务都会有自己的固定公网IP地址,这个先决条件简化了 WebRTC 中 peerConnection 的建立过程,可以把这种场景理解为 P2P 通信的一端确定在 NAT 前面,并且有固定IP地址的特殊情况,这时对于 SFU 服务没有必要再去收集自己的 iceCandidate,所以 ICE 协议(RFC5245) 中定义了一种 lite 实现方式,简化了有固定IP公网地址一端的 ice 实现方式,只要满足固定公网IP的前提条件,通过 lite 方式实现一个 ice server,完全实现 ice 协议的对端将毫无感知的与 lite 实现建立常规的 peerConnection 通信信道。
ice-lite 有以下几个特点:
- ICE 的 Lite 实现不需要实现 Candidate 的收集过程;只需要提供 Host Candidate;
- 不需要实现连通性检查或者状态机,只需要实现连通性检查的Response;
- ICE Lite 在交互过程中只能是 controlled 角色,并且不能使用 USE-CANDIDATE 来做 ice nomination;
常规的 SFU 都会自身集成一个 ice lite 实现的 ice server,所以交互过程中省去了 ice server 的独立角色。SFU 的 SDP 信息中会携带自身的 Host Candidate 和 ‘a=ice-lite’ 属性,当对端收到 ‘a=ice-lite’ 属性后会自动将自己设置成 controlling 角色,主动与 SFU 的 ice agent 进行交互。同时 SFU 也不需要对端的 iceCandidate 信息,因为在对端发起 stun binding 测试连通性的时候就可以获取对端的信息,便可以建议一个 Transport tuple 用来维持 peerConnection 通信。
下图是 SFU 模式下 peerConnectionn 的建立过程:
通过上面的流程图可以看出,在 SFU 场景下 peerConnection 的过程得到了简化,由于 SFU 具有固定公网 IP 地址和 ice-lite 实现的 ice server 加持,省去了 peerConnection 双方单独交换 iceCandidate 的流程,并且可以缩短 peerConnection 的建立时间。
最佳实践
mediasoup 是一个典型的 SFU 服务,可以通过搭建 mediasoup-demo 来演示上面的使用场景,具体请参考