开始使用对等连接

对等连接是 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 收集的状态(newgatheringcomplete)。

虽然对等方可以等到 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!
    }
});

RTCPeerConnection API 文档