WebRTC 连接

本文介绍了各种 WebRTC 相关协议如何相互作用,以创建连接并在对等方之间传输数据和/或媒体。

注意: 本页面需要大量重写以确保结构完整性和内容完整性。这里有很多很好的信息,但由于目前这里有点像一个垃圾场,所以组织方式一团糟。

信令

不幸的是,WebRTC 无法在没有中间服务器的情况下创建连接。我们称之为 信令通道信令服务。它是在建立连接之前用于交换信息的任何通信通道,无论是通过电子邮件、明信片还是信鸽。这由您决定。

我们需要交换的信息是 Offer(提议)和 Answer(应答),其中只包含下面提到的 SDP

作为连接发起方的对等方 A 将创建一个 Offer。然后,他们将使用所选的信令通道将此 Offer 发送给对等方 B。对等方 B 将从信令通道接收 Offer 并创建一个 Answer。然后,他们将通过信令通道将其发送回对等方 A。

会话描述

WebRTC 连接上端点的配置称为 会话描述。该描述包括有关正在发送的媒体类型、其格式、正在使用的传输协议、端点的 IP 地址和端口以及描述媒体传输端点所需的其他信息。此信息使用 会话描述协议 (SDP) 进行交换和存储;如果您想了解 SDP 数据的格式详细信息,可以在 RFC 8866 中找到。

当用户向另一个用户发起 WebRTC 呼叫时,会创建一个特殊描述,称为 提议 (offer)。此描述包括有关呼叫方提议的呼叫配置的所有信息。接收方然后用 应答 (answer) 进行响应,这是对其呼叫端点的描述。通过这种方式,两台设备相互共享交换媒体数据所需的信息。这种交换使用交互式连接建立 (ICE) 进行处理,该协议允许两台设备使用中介来交换提议和应答,即使两台设备被网络地址转换 (NAT) 分隔。

因此,每个对等方都保留两个描述:本地描述 (local description),描述自身;以及 远程描述 (remote description),描述呼叫的另一端。

提议/应答过程不仅在首次建立呼叫时执行,而且在呼叫的格式或其它配置需要更改的任何时候也执行。无论是新呼叫还是重新配置现有呼叫,以下是交换提议和应答必须发生的基本步骤,暂时不考虑 ICE 层

  1. 呼叫者通过 MediaDevices.getUserMedia 捕获本地媒体
  2. 呼叫者创建 RTCPeerConnection 并调用 RTCPeerConnection.addTrack() (因为 addStream 已弃用)
  3. 呼叫者调用 RTCPeerConnection.createOffer() 创建一个提议。
  4. 呼叫者调用 RTCPeerConnection.setLocalDescription() 将该提议设置为 本地描述 (即连接本地端的描述)。
  5. 在 setLocalDescription() 之后,呼叫者要求 STUN 服务器生成 ICE 候选。
  6. 呼叫者使用信令服务器将提议传输给预期的呼叫接收者。
  7. 接收者收到提议并调用 RTCPeerConnection.setRemoteDescription() 将其记录为 远程描述 (连接另一端的描述)。
  8. 接收者执行其呼叫端所需的任何设置:捕获其本地媒体,并通过 RTCPeerConnection.addTrack() 将每个媒体轨道附加到对等连接。
  9. 接收者然后通过调用 RTCPeerConnection.createAnswer() 创建一个应答。
  10. 接收者调用 RTCPeerConnection.setLocalDescription(),传入创建的应答,以将该应答设置为其本地描述。接收者现在知道连接两端的配置。
  11. 接收者使用信令服务器将应答发送给呼叫者。
  12. 呼叫者收到应答。
  13. 呼叫者调用 RTCPeerConnection.setRemoteDescription() 将应答设置为其呼叫端的远程描述。它现在知道两个对等方的配置。媒体开始按配置流动。

待处理和当前描述

更深入地了解该过程,我们发现 localDescriptionremoteDescription(返回这两个描述的属性)并不像它们看起来那么简单。因为在重新协商期间,一个提议可能因其提出了不兼容的格式而被拒绝,所以每个端点都需要能够提出新格式,但在被另一个对等方接受之前不能实际切换到该格式。因此,WebRTC 使用 待处理当前 描述。

当前描述(由 RTCPeerConnection.currentLocalDescriptionRTCPeerConnection.currentRemoteDescription 属性返回)表示连接当前实际使用的描述。这是双方完全同意使用的最新连接。

待处理描述(由 RTCPeerConnection.pendingLocalDescriptionRTCPeerConnection.pendingRemoteDescription 返回)表示在分别调用 setLocalDescription()setRemoteDescription() 后正在考虑的描述。

当读取描述(由 RTCPeerConnection.localDescriptionRTCPeerConnection.remoteDescription 返回)时,如果存在待处理描述(即,待处理描述不为 null),则返回 pendingLocalDescription/pendingRemoteDescription 的值;否则,返回当前描述(currentLocalDescription/currentRemoteDescription)。

当通过调用 setLocalDescription()setRemoteDescription() 更改描述时,指定的描述被设置为待处理描述,WebRTC 层开始评估它是否可接受。一旦提议的描述达成一致,currentLocalDescriptioncurrentRemoteDescription 的值将更改为待处理描述,并且待处理描述再次设置为 null,表示没有待处理描述。

注意: pendingLocalDescription 不仅包含正在考虑的提议或应答,还包含自提议或应答创建以来已经收集到的任何本地 ICE 候选。同样,pendingRemoteDescription 包含通过调用 RTCPeerConnection.addIceCandidate() 提供的任何远程 ICE 候选。

有关这些属性和方法的更多具体信息,请参阅各个文章;有关 WebRTC 支持的编解码器以及与哪些浏览器兼容的信息,请参阅 WebRTC 使用的编解码器。编解码器指南还提供了帮助您选择最适合您需求的编解码器的指导。

ICE 候选

除了交换媒体信息(上面在提议/应答和 SDP 中讨论)之外,对等方还必须交换有关网络连接的信息。这被称为 ICE 候选,并详细说明了对等方能够通信的可用方法(直接或通过 TURN 服务器)。通常,每个对等方会首先提出其最佳候选,然后向下推迟到较差的候选。理想情况下,候选是 UDP(因为它更快,并且媒体流能够相对容易地从中断中恢复),但 ICE 标准也允许 TCP 候选。

注意: 通常,使用 TCP 的 ICE 候选仅在 UDP 不可用或受限制(使其不适合媒体流)时才会使用。但是,并非所有浏览器都支持 ICE over TCP。

ICE 允许候选者表示通过 TCPUDP 的连接,通常更倾向于 UDP(并且得到更广泛的支持)。每个协议都支持几种类型的候选者,候选者类型定义了数据如何从对等点传输到对等点。

UDP 候选类型

UDP 候选(protocol 设置为 udp 的候选)可以是以下类型之一:

主机

主机候选是指其 ip 地址是远程对等方的实际直接 IP 地址的候选。

prflx

对等反射候选是指其 IP 地址来自两个对等方之间的对称 NAT,通常作为在 trickle ICE(即在主要信令之后但在连接验证阶段完成之前发生的额外候选交换)期间的额外候选。

srflx

服务器反射候选由 STUN/TURN 服务器生成;连接发起者向 STUN 服务器请求一个候选,该服务器将请求转发到远程对等方的 NAT,后者创建并返回一个 IP 地址位于远程对等方本地的候选。然后,STUN 服务器以一个 IP 地址与远程对等方无关的候选回复发起者的请求。

relay

中继候选的生成方式与服务器反射候选 ("srflx") 类似,但使用的是 TURN 而不是 STUN

TCP 候选类型

TCP 候选(即 protocoltcp 的候选)可以是以下类型:

激活

传输将尝试打开出站连接,但不会接收入站连接请求。这是最常见的类型,也是大多数用户代理将收集的唯一类型。

passive

传输将接收入站连接尝试,但不会尝试自身连接。

so

传输将尝试与其对等方同时打开连接。

选择候选对

ICE 层选择两个对等体之一作为 控制代理。这是 ICE 代理,它将对用于连接的候选对做出最终决定。另一个对等体称为 受控代理。您可以通过检查 RTCIceCandidate.transport.role 的值来识别您的连接端是哪个,尽管通常哪个是哪个并不重要。

控制代理不仅负责对使用哪个候选对做出最终决定,而且还负责在必要时使用 STUN 和更新的提议向受控代理发送该选择的信号。受控代理只是等待被告知要使用哪个候选对。

重要的是要记住,一个 ICE 会话可能会导致控制代理选择多个候选对。每次它这样做并将该信息与受控代理共享时,两个对等方都会重新配置其连接以使用新候选对描述的新配置。

一旦 ICE 会话完成,当前生效的配置就是最终配置,除非发生 ICE 重置。

在每代候选者结束后,会以 RTCIceCandidate 的形式发送一个候选者结束通知,其 candidate 属性是一个空字符串。此候选者仍应使用 addIceCandidate() 方法照常添加到连接中,以便将该通知传递给远程对等方。

当当前协商交换中不再有任何候选者时,会通过发送一个 RTCIceCandidate 来发送候选者结束通知,其 candidate 属性为 null。此消息 不需要 发送给远程对等方。它是状态的遗留通知,可以通过观察 iceGatheringState 变为 complete,或者通过观察 icegatheringstatechange 事件来检测。

当出现问题时

在协商过程中,总会有事情不如意的时候。例如,在重新协商一个已经激活的连接时——例如,为了适应不断变化的硬件或网络配置——协商可能会陷入僵局,或者发生某种形式的错误,从而完全阻止协商。此外,还可能存在权限问题或其他问题。

ICE 回滚

当重新协商一个已经激活的连接,并且出现协商失败的情况时,您并不希望终止正在运行的通话。毕竟,您很可能只是试图升级或降级连接,或者对正在进行的会话进行其他调整。在这种情况下,终止通话将是过度的反应。

相反,您可以启动 ICE 回滚。回滚将 SDP 提议(以及由此推断的连接配置)恢复到连接的 signalingStatestable 时的配置。

要以编程方式发起回滚,请发送一个 typerollback 的描述。描述对象中的任何其他属性都将被忽略。

此外,当曾经创建过提议的对等方收到远程对等方的提议时,ICE 代理将自动发起回滚。换句话说,如果本地对等方处于 have-local-offer 状态,表示本地对等方之前 发送 了一个提议,那么使用 收到的 提议调用 setRemoteDescription() 将触发回滚,以便协商从远程对等方作为呼叫方切换到本地对等方作为呼叫方。

ICE 重启

了解 ICE 重启 过程。

复杂图表中的整个交换过程

A complete architectural diagram showing the whole WebRTC process.

原始来源