因为新冠疫情,远程工作已成为越来越普遍的现象。随着越来越多的公司和团队开始线上工作,大家对在线协作工具的要求也在不断提高,尤其在视频会议应用方面。
为构建视频会议解决方案,我采用了一个 CLI 工具,这个工具可以同时分享屏幕和代码,项目中的每个变化都会实时反映在通话中:
开始我不太了解视频会议。经过仔细研究,我知道了 WebRTC(或 RTC),它是一个实时通讯协议。
虽然 WebRTC 不是专门为视频会议设计的,但设计之初必然考虑到了这一点。根据当前的标准,延迟时间小于 1 秒钟,即可称为“实时”。WebRTC 是目前最快的视频会议解决方案,而且是开源软件,所以它是免费的。
其他解决方案在延迟方面的表现都不如 WebRTC,下图显示了延迟对比,大家可以看一看 WebRTC 到底有多快:
资料来源:Wowza
开始使用 WebRTC 时,我发现它的架构非常复杂。要保证其正常运行,需要安装几个应用。它涉及很多部件,不像 REST 或 web sockets 那样,通过单一方法集成起来。
我跟着教程操作,但很快意识到哪里好像不对。当我试图在系统中做一些调整(比如添加 2 人以上通话的功能)时,就出现了问题。目前,我得到的重要结论是:必须了解 WebRTC 是如何在架构层面上(而不是 API 上)运行的。
因此,本文我想先探讨一下 WebRTC 的操作流程,然后概述涉及的组件,以及组件间的通信方式。因为关于 API 的教程和文章已经有很多,但关于架构的优秀文章较少,所以本文不再对 API 进行深入的讨论。
WebRTC 概述
WebRTC 是一个让浏览器直接通讯的协议,它包括一组规范过程的类和方法,从 Chrome 23 时就存在:
RTCPeerConnection——最原始的 WebRTC 类
除了让通信流程标准化,浏览器还可以快速安全地访问硬件,这是对 WebRTC 的补充,可以推流屏幕、麦克风和相机。但是每个操作系统和硬件都需要不同且复杂的配置,通常需要安装外部插件或二进制文件,所以会非常复杂。起初,我试着与 ffmpeg 进行屏幕共享,也成功了,但是遇到了许多兼容性问题。
对等连接
WebRTC 基于 p2p架构(点对点),通话参与者负责将数据从一端传输到另一端,不依赖“中间人”(大多数情况下;详见下文)。假设一个参与者断开连接,其他人会继续传输数据,这与传统通讯不同。在传统通讯中,如果与服务器之间的连接断开,数据就会停止传输。此外,对等点在地理位置上更加接近,所以数据传输的距离较近。
因此,当我进入对话时,必须用专门的实例来代表每个对等点。假设对话中共有 4 个对等点(包括我自己),就会有 3 个对等点实例,每个实例都直接链接到不同的浏览器,如下图:
4 个对等点的系统
信令服务器
在通话过程中,需要跟踪加入或退出对话的人员,并分别创建或销毁连接。为了跟踪这些事件,我们需要一个信令服务器 。
信令服务器专门用于在两个或更多需要通信的对等点之间建立初始连接。连接建立后,不一定要在后续的通信中使用它;但如果你想发信号通知其他事件,例如对等点已断开连接,则可以使用它,一切由你决定。
信令服务器有多种实现方式,你只需要搭建对等点 A 和 B 之间的桥梁。你可以使用 REST,或通过电子邮件复制粘贴等方式,但通常会使用 Web sockets,因为这样可以随时自动初始化通信:
我加入对话后,会通过信令服务器广播,让所有人都知道
SDP
一旦有人加入了对话,我们就需要交换彼此系统的信息以建立连接。此信息基于 SDP 协议(会话描述协议),并且包含其所属对等点的详细信息,比如使用的代理、支持的硬件、可交换的媒体类型等。SDP config 是一个简单的键值对象:
SDP 配置既可以代表应答,也可以代表提议 。每当我们想建立连接时,就会发送提议,然后获得应答。提议与应答是双向的,无论哪方先初始化连接,结果是一样的。
重要的是要跟踪有问题的 SDP 配置代表哪一端。初始化对等实例需要本地描述和远端描述。本地描述代表我们,远端描述代表另一端,合在一起就能成功建立连接:
将 2 个对等点与其 SDP 连接起来
ICE 候选对象
一个对等点可能有多个通信传输。一个用户可能有多个私有 IP/端口、多个公共 IP/端口、各种协议以及一个或多个反向代理等。一旦我们创建了 SDP 提议,WebRTC 就会查找每个可以到浏览器的通信传输,它们统称为 ICE 候选对象( 交互式连接建立):
实际的 RTCIceCandidate 实例
ICE 候选对象是另一个应该添加到 SDP 的键值组。我们可以等 WebRTC 找到所有候选者,并发送完整的 SDP;也可以通过信令服务器发送每个检测到的 ICE 候选对象,并逐步扩展 SDP——两个方法均有效。WebRTC 应该知道如何在 ICE 之间轮换,并选择最可行的方法。
默认情况下,WebRTC 会优先使用基于 UDP (用户数据报协议)的 ICE。在 TCP(传输控制协议——HTTP 使用的传统协议)中,需要把前面的数据包传输完成才能传输下一个数据包。UDP 与 TCP 不同,它不考虑前面的数据包的状态,会持续传输数据包,加快了通信速度。
创建 SDP 后,WebRTC 便开始寻找 ICE 候选对象
NAT
如今,大多数计算机都没有直接连接到全球网络,通常都经过 NAT 层(网络地址转换)。通过路由器进行传输时,机器的私有 IP /端口将转换为公共 IP /端口。
因为 WebRTC 想在两端间建立尽可能直接的连接,所以任何一方经过代理时都会增加一些复杂性。我们来看一下不同的 NAT 配置,并看看如何用它们建立直接连接(我直接使用了 dh2i.com 上的定义):
普通(完全圆锥形)NAT
完全圆锥形 NAT 是将内部 IP 地址和端口的所有请求,都映射到同一个外部 IP 地址和端口的 NAT。另外,通过将数据包发送到映射的外部地址,任何外部主机都可以将数据包发送到内部主机。
具有目标 IP /端口的对等点通过向路由器的一个公共 IP /端口发出请求,与我们建立连接,然后将这个公共 IP /端口转换为我们的机器的私人 IP /端口
受限圆锥形 NAT
受限圆锥形 NAT 是将内部 IP 地址和端口的所有请求,都映射到同一个外部 IP 地址和端口的 NAT。与完全圆锥形 NAT 不同的是:只有在内部主机曾向 IP 地址 X 发送过数据包时,外部主机才可以将数据包发送到内部主机。
端口受限圆锥形 NAT
端口受限圆锥形 NAT 与受限圆锥形 NAT 类似,区别在于限制的内容包括端口号。只有内部主机曾将数据包发送到 IP 地址 X 和端口 P 时,外部主机才能将具有源 IP 地址 X 和源端口 P 的数据包发送到内部主机。
对称 NAT
对称 NAT 是把同一内部 IP 地址和端口到特定目标 IP 地址和端口的所有请求,都映射到同一外部 IP 地址和端口。如果同一主机将具有相同源地址和端口的数据包发送到另一个目的地,则需要使用不同的映射。此外,只有接收到数据包的外部主机才能将 UDP 数据包发送回内部主机。
我想给这些定义添加一点内容。路由器将使用 NAT 表管理其状态,该表将包含所有交易的历史记录;每次提出请求,都会创建一个条目,并添加到表中。该条目通常将包含以下信息:
- 私有 IP /端口
- 公共 IP /端口
- 目的 IP /端口
这些信息对理解接下来的原则非常重要。
STUN
如果我们的计算机连接到 NAT 层,就需要公共 IP /端口来创建 ICE 候选对象。因此,WebRTC 能让我们在初始化 WebRTC 连接时,指定 STUN 服务器 URL(NAT 的会话穿越应用程序)。
STUN 是一套标准化的方法,包括一个网络协议,用于在实时语音、视频、消息和其他交互式通信应用中穿越网络地址转换(NAT)。——维基百科
它实际上就是在返回公共 IP /端口。因此,当我们尝试在两个对等点之间建立连接时,会出现以下情况:
- 使对等点 A 和 B 具有完全圆锥形 NAT。
- 对等点 A 将使用 STUN 服务器获取其公共 IP /端口的信息。
- 对等点 A 将使用信令服务器将该信息发送给对等点 B。
- 对等 B 将获得该信息,并尝试与对等点 A 建立连接。
- 反过来一样。
资料来源:MDN
因为 STUN 服务器的功能不多,所以价格便宜。使用它们不需要任何身份验证,且通常都是免费的。
如果 2 个对等点在完全圆锥形 NAT 上运行,就只需要公共 IP /端口即可建立连接。路由器将查看附加到传入请求的公共 IP /端口,如果可以将其与私用 IP /端口匹配,就可以接受连接。
打孔
其他 2 个受限圆锥形 NAT 与对等点的连接方式与完全圆锥形 NAT 类似,不过有一个小限制。它们需要知道自己的公共 IP /端口,并且还要确保 NAT 表中存在传入请求的目标 IP /端口。这与完全圆锥形 NAT 不同。在完全圆锥形 NAT 中,路由器基本上信任所有人,而受限圆锥形只信任尝试与之建立连接的人员。
这就产生了一个问题——如果有两个对等点从未建立过连接,那么它们如何才能建立连接?
为解决此问题,我们使用了一种称为打孔 (也称为穿孔)的技术:
- 使对等点 A 具有受限圆锥形 NAT,对等点 B 具有完全圆锥形 NAT;
- 对等点 A 将使用 STUN 服务器获取其公共 IP /端口的信息;
- 对等点 A 将使用信令服务器将该信息发送给对等点 B;
- 对等点 B 将获得该信息,并尝试与对等点 A 建立连接;
- 对等点 B 无法建立连接,但会将对等点 A 的公共信息存储在其 NAT 表中;
- 对等点 A 将尝试与对等点 B 建立连接。由于对等点 A 在对等点 B 的 NAT 表中已经存在,因此该连接会被接受;
- 对等点 A 将存储有关对等点 B 的公共信息;
- 对等点 B 现在可以与对等点 A 建立连接。
TURN
在处理对称 NAT 时,我们可以完全抛弃 p2p 和浏览器直接通信,以下是连接过程:
- 使对等点 A 具有对称 NAT,对等点 B 具有完全圆锥形 NAT;
- 对等点 A 将使用 STUN 服务器获取其公共 IP /端口的信息;
- 对等点 A 将使用信令服务器将该信息发送给对等点 B;
- 对等点 B 将获得该信息,并尝试与对等点 A 建立连接;
- 对等点 B 将无法建立连接,但会将对等点 A 的公共信息存储在其 NAT 表中;
- 对等点 A 将尝试与对等点 B 建立连接。但是,因为存储在其 NAT 表中的公共信息与它实际接收到的公共信息不同,所以对等点 B 将拒绝对等点 A。
你会发现,当一个对等点的公共 IP /端口不是静态的时,我们将无法实现直接的浏览器通信。这就是 WebRTC 能让我们指定 TURN 服务器 URL 的原因。
TURN (Travesel Using Relays around NAT)是一种协议,能帮助多媒体应用程序穿越网络地址转换器(NAT)或防火墙。——维基百科
TURN 实际上是为了避免直接通信。它使用反向代理,这样公共 IP /端口保持不变,我们就可以建立连接,这不仅会使连接变慢且效率降低,还会大大提高连接成本。因此,TURN ICE 的优先级始终最低。
假设你与数十个对等点进行通信,每个对等点都通过 TURN 发送数据包。你必须为所有正在传输的数据付费,导致成本很高,尤其是当服务器不在最近的区域时,这就是 TURN 服务器永远不会免费的原因,并且始终使用身份验证机制对其进行保护。
来源:MDN
现在,两个或更多对等点就可以建立连接并彼此通信了。
希望大家喜欢这篇文章!如果你也在寻找共享屏幕和代码的简便方法,建议参考 Git Streamer。
原文作者:Eytan Manor
原文链接:https://eytanmanor.medium.com/an-architectural-overview-for-web-rtc-a-protocol-for-implementing-video-conferencing-e2a914628d0e