WebRTC 连接

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

注意: 此页面需要大量重写以确保结构完整性和内容完整性。这里有很多信息很好,但组织混乱,因为现在这有点像垃圾箱。

信令

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

我们需要交换的信息是 Offer 和 Answer,它们只包含下面提到的 SDP

对等方 A 将是连接的启动者,它将创建一个 Offer。然后,他们将使用所选的信令通道将此 Offer 发送到对等方 B。对等方 B 将从信令通道接收 Offer 并创建一个 Answer。然后,他们将此 Answer 发送回对等方 A,沿着信令通道。

会话描述

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

当用户开始向另一个用户进行 WebRTC 通话时,会创建一个名为offer的特殊描述。此描述包含有关呼叫方为呼叫提出的所有配置信息。然后,接收者以answer形式进行响应,其中包含其通话端点的描述。这样,两台设备之间就可以共享进行媒体数据交换所需的信息。这种交换使用交互式连接建立 (ICE) 进行处理,该协议允许两台设备使用中介交换 offer 和 answer,即使这两台设备被网络地址转换 (NAT) 隔开。

因此,每个对等方都保留着两个描述:本地描述,描述自身,以及远程描述,描述通话的另一端。

offer/answer 过程在首次建立通话时执行,但在通话格式或其他配置需要更改时也会执行。无论是否为新通话,还是重新配置现有通话,这些都是必须执行的基本步骤,以便交换 offer 和 answer,暂时不考虑 ICE 层。

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

待处理和当前描述

深入了解这个过程,我们会发现 localDescriptionremoteDescription 这些返回这两个描述的属性并没有看起来那么简单。因为在重新协商期间,offer 可能会由于它提出的格式不兼容而被拒绝,因此每个端点都必须能够提出新的格式,但只有在被另一个对等方接受后才能真正切换到该格式。出于这个原因,WebRTC 使用待处理当前描述。

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

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

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

通过调用 setLocalDescription()setRemoteDescription() 来更改描述时,指定的描述将设置为待处理描述,并且 WebRTC 层开始评估它是否可接受。一旦提出的描述得到双方同意,currentLocalDescriptioncurrentRemoteDescription 的值将更改为待处理描述,并且待处理描述将再次设置为 null,表示不再存在待处理描述。

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

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

ICE 候选

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

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

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

UDP 候选类型

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

host

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

prflx

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

srflx

服务器反射候选是由 STUN/TURN 服务器生成的;连接的启动者向 STUN 服务器请求候选,STUN 服务器将请求转发到远程对等方的 NAT,远程对等方创建并返回一个其 IP 地址在远程对等方本地有效的候选。然后,STUN 服务器用一个其 IP 地址与远程对等方无关的候选来回复启动者的请求。

relay

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

TCP 候选类型

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

active

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

passive

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

so

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

选择候选对

ICE 层选择两个对等方中的一个作为 **控制代理**。这是 ICE 代理,它将最终决定使用哪个候选对进行连接。另一个对等方称为 **受控代理**。您可以通过检查 RTCIceCandidate.transport.role 的值来确定您的连接端是哪个,尽管通常情况下哪个是哪个并不重要。

控制代理不仅负责做出最终决定使用哪个候选对,还负责通过使用 STUN 和更新的提议(如果需要)向受控代理发出信号。受控代理只等待指示使用哪个候选对。

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

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

在每次生成候选结束时,都会以 RTCIceCandidate 的形式发送一个候选结束通知,该通知的 candidate 属性为空字符串。此候选仍然应该像往常一样使用 addIceCandidate() 方法添加到连接中,以便将该通知传递给远程对等方。

当在当前协商交换过程中不再期望有任何更多候选时,会通过传递一个 RTCIceCandidate 来发送候选结束通知,该通知的 candidate 属性为 null。此消息 *不需要* 发送到远程对等方。这是一个状态的传统通知,可以通过监视 iceGatheringState 更改为 complete 以及监视 icegatheringstatechange 事件来检测。

出现问题时

在协商期间,有时事情会无法正常进行。例如,当重新协商连接时(例如,为了适应不断变化的硬件或网络配置),协商可能会陷入僵局,或者可能会发生某种形式的错误,完全阻止协商。同样,也可能存在权限问题或其他问题。

ICE 回滚

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

相反,您可以启动 **ICE 回滚**。回滚将 SDP 提议(以及扩展的连接配置)恢复到连接的 signalingState 最后一次为 stable 时的配置。

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

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

ICE 重启

了解 ICE 重启 流程。

复杂图中的整个交换过程