对等连接是 WebRTC 规范的一部分,用于处理连接不同计算机上的两个应用,以便使用点对点协议进行通信。对等方之间的通信可以是视频、音频或任意二进制数据(对于支持 RTCDataChannel
API 的客户端)。为了了解两个对等方如何连接,两个客户端都需要提供 ICE 服务器配置。这是一个 STUN 或 TURN 服务器,其作用是为每个客户端提供 ICE 候选,然后将其转移到远程对等方。这种 ICE 候选的传输通常称为信号传递。
信令
WebRTC 规范包含用于与 ICE(互联网连接建立)服务器通信的 API,但信号组件不在其中。需要使用信号来让两个对等方分享它们应如何连接。通常,此问题可通过基于 HTTP 的常规 Web API(即 REST 服务或其他 RPC 机制)来解决,在这种情况下,Web 应用可以在发起对等连接之前中继必要的信息。
以下代码段展示了如何使用此虚构信号服务异步发送和接收消息。本指南的其余示例中会根据需要使用此值。
// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
// New message from remote client received
});
// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');
信令可以通过多种不同的方式实现,WebRTC 规范不偏向任何特定解决方案。
发起对等连接
每个对等连接由 RTCPeerConnection
对象处理。此类的构造函数接受单个 RTCConfiguration
对象作为参数。此对象定义了如何设置对等连接,并且应包含要使用的 ICE 服务器的相关信息。
创建 RTCPeerConnection
后,我们需要创建 SDP 报价或响应,具体取决于我们是发起方还是接收方。创建 SDP 报价或响应后,必须通过其他渠道将其发送给远程对等方。将 SDP 对象传递给远程对等方称为信令,WebRTC 规范未涵盖此过程。
如需从调用方发起对等连接设置,我们需要创建一个 RTCPeerConnection
对象,然后调用 createOffer()
来创建 RTCSessionDescription
对象。此会话描述会使用 setLocalDescription()
设置为本地说明,然后通过信令通道发送到接收端。我们还为信号通道设置了监听器,以便在接收端收到对我们提供的会话描述的回答时进行处理。
async function makeCall() {
const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
if (message.answer) {
const remoteDesc = new RTCSessionDescription(message.answer);
await peerConnection.setRemoteDescription(remoteDesc);
}
});
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signalingChannel.send({'offer': offer});
}
在接收端,我们会等待传入的优惠,然后再创建 RTCPeerConnection
实例。完成后,我们使用 setRemoteDescription()
设置收到的优惠。接下来,我们调用 createAnswer()
来创建对收到的优惠的回答。此答案会使用 setLocalDescription()
设置为本地说明,然后通过信令服务器发送到调用方。
const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
if (message.offer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
signalingChannel.send({'answer': answer});
}
});
两个对等体都设置了本地和远程会话说明后,它们就会知道远程对等体的功能。这并不意味着对等方之间的连接已就绪。为此,我们需要在每个对等点收集 ICE 候选项,并将其(通过信令通道)传输到另一个对等点。
ICE 候选
在两个对等方能够使用 WebRTC 进行通信之前,它们需要交换连接信息。由于网络条件可能会因多种因素而异,因此通常使用外部服务来发现可能的候选对象以连接到对等方。此服务称为 ICE,使用的是 STUN 或 TURN 服务器。STUN 是“Session Traversal Utilities for NAT”(适用于 NAT 的会话穿越实用程序)的缩写,通常在大多数 WebRTC 应用中间接使用。
TURN(Traversal Using Relay NAT)是一种更高级的解决方案,它集成了 STUN 协议,并且大多数基于 WebRTC 的商业服务都使用 TURN 服务器在对等方之间建立连接。WebRTC API 直接支持 STUN 和 TURN,并归入更完整的术语“互联网连接建立”下。创建 WebRTC 连接时,我们通常会在 RTCPeerConnection
对象的配置中提供一个或多个 ICE 服务器。
涓涓细流 ICE
创建 RTCPeerConnection
对象后,底层框架会使用提供的 ICE 服务器收集用于建立连接的候选对象 (ICE 候选对象)。RTCPeerConnection
上的事件 icegatheringstatechange
用于指示 ICE 收集的状态(new
、gathering
或 complete
)。
虽然对等方可以等到 ICE 收集完成,但通常使用“trickle ice”技术并在发现每个 ICE 候选对象时将其传输到远程对等方会更高效。这将显著缩短对等连接的设置时间,并让视频通话能够在更短的延迟时间内开始。
如需收集 ICE 候选连接,只需为 icecandidate
事件添加监听器即可。在该监听器上发出的 RTCPeerConnectionIceEvent
将包含 candidate
属性,该属性表示应发送到远程对等方的新候选人(请参阅信号)。
// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
if (event.candidate) {
signalingChannel.send({'new-ice-candidate': event.candidate});
}
});
// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
if (message.iceCandidate) {
try {
await peerConnection.addIceCandidate(message.iceCandidate);
} catch (e) {
console.error('Error adding received ice candidate', e);
}
}
});
已建立连接
收到 ICE 候选后,我们应该会预期对等连接的状态最终会变为已连接状态。为了检测这一点,我们向 RTCPeerConnection
添加了一个监听器,用于监听 connectionstatechange
事件。
// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
if (peerConnection.connectionState === 'connected') {
// Peers connected!
}
});