开始使用对等连接

点对点连接是 WebRTC 规范的一部分,该规范旨在对点一台计算机上的两台应用进行连接,以使用点对点协议进行通信。对等设备之间的通信可以是视频、音频或任意二进制数据(适用于支持 RTCDataChannel API 的客户端)。为了发现两个对等端如何连接,两个客户端都需要提供 ICE Server 配置。这是 STUN 或 TURN 服务器,其作用是向每个客户端提供 ICE 候选对象,然后这些客户端将被传输到远程对等方。这种转移 ICE 候选对象的方式通常称为信号。

信令

WebRTC 规范包含用于与 ICE(互联网连接建立)服务器通信的 API,但信令组件并不属于该组件。需要发出信号才能让两个对等网络共享它们之间的连接方式。这通常可以通过基于 HTTP 的常规 Web API(即 REST 服务或其他 RPC 机制)解决,在此过程中,网络应用可在发起对等连接之前中继必要的信息。

以下代码段展示了如何使用虚构信号服务异步发送和接收消息。必要时,本指南的其余示例将使用该方法。

// 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 交换连接信息,然后才能使用 WebRTC 进行通信。由于网络条件可能因多种因素而发生变化,因此通常使用外部服务来发现连接到对等网络的潜在候选对象。此服务称为 ICE,使用的是 STUN 或 TURN 服务器。STUN 代表用于 NAT 的会话遍历实用程序,通常在大多数 WebRTC 应用中间接使用。

TURN(Traversal using Relay NAT)是一种整合了 STUN 协议的更高级解决方案,大多数基于 WebRTC 的商业服务都使用 TURN 服务器在对等方之间建立连接。WebRTC API 直接支持 STUN 和 TURN,在更完整的术语“互联网连接建立”下收集。创建 WebRTC 连接时,我们通常会在 RTCPeerConnection 对象的配置中提供一个或多个 ICE 服务器。

Trickle ICE

创建 RTCPeerConnection 对象后,底层框架会使用提供的 ICE 服务器收集连接建立的候选对象(ICE 候选对象)。RTCPeerConnection 上的事件 icegatheringstatechange 会指示 ICE 收集的状态为(newgatheringcomplete)。

虽然对等设备可以等待 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 文档