来源:Youtube:Hussein Nasser 翻译整理:卢冰聪 在这个视频中作者概述了 WebRTC 的基本内容并详细讨论了部分概念,大家可以学习到有关 NAT、STUN、TURN、ICE、SDP 和信令(Signaling)的相关知识,同时作者还展示了一个 Demo。最后,作者讨论了 WebRTC 的优势和劣势,并介绍了部分扩展内容。
目录
- 1. WebRTC 概述
- 2. WebRTC 揭秘
- 2.1 Network Address Translation: NAT
- 2.2 Session Traversal Utilities for NAT:STUN
- 2.3 Traversal Using Relays around NAT: TURN
- 2.4 Interactive Connectivity Establishment: ICE
- 2.5 Session Description Protocol: SDP
- 2.6 信令交换:Signaling
- 工作流程总结
- 3. Demo
- 4. WebRTC的优缺点
- 1. 优点
- 2. 缺点
- 5. 扩展内容
- 5.1 Media API
- 5.2 onIceCandidate 和 addIceCandidate
- 5.3 自定义 TURN 和 STUN 服务器
- 5.4 公共 STUN 服务器
WebRTC (Web Real-Time Communication)是一个免费、开源的项目,通过简单的应用程序编程接口(API)为 Web 浏览器和移动应用程序提供实时通信(RTC)。这也表明了 WebRTC 设计的目标就是“设计一种通过尽量短的、延迟尽量低的路径进行 P2P 通信的协议,提供一种简单的、能让所有人使用的 API”。一旦你把它放入浏览器,它就是标准;一旦它成为了标准,开发时会遇到的“摩擦”就会消失。
我追踪 WebRTC 这项技术大概已经两年了,听众们为我提供了大量优质的资源,也提出了很多优秀的问题。应大家的呼声,我做出了这期视频,为大家提供一个 WebRTC 的基本教程。我将按以下顺序进行讲解:
- WebRTC 概述
- WebRTC 揭秘:NAT、STUN、TURN、ICE、SDP、信令
- Demo
- WebRTC的优缺点
- 扩展内容
1. WebRTC 概述
首先想到的问题是我们为何要建立 WebRTC?
建立它的理由是人们需要用一种标准的、低延迟的方式来传递媒体数据(视频&音频)。所谓“标准的”意味着我们需要简单易使用的 API;而所谓“低延迟的”意味着需要一种合适的协议,UDP 显然是一个好的选择,因为 UDP 没有过多的应答过程(Acknowledgment)。但我们需要的协议要比 UDP 更好,要能支持 P2P 的通信。因为一旦依赖服务器来传递内容就会因为反向代理或者穿透引入额外的延迟,用户需要进行终止、观察、处理、转化流等操作,这些都会造成额外消耗。对于视频传输、特别是直播、会话等场景,用户希望内容到达得越快越好,所以 P2P 是最快的路径。
此外,WebRTC 也旨在实现浏览器之间丰富的沟通。浏览器已经发展了很长时间,它“拥有”大量的优质视频,它可以访问摄像头和麦克风,这些特性都值得被开发利用。用户不需要写自己的应用,而是基于 WebRTC 的标准 API 便可以轻松使用。不仅是浏览器,在移动设备和 IoT 设备通信时也同样。
那么在 WebRTC 中究竟发生了哪些事呢?
举个例子,A 想要与 B 进行通信,但 A 与 B 之间“互不相识”。所以 A 首先需要找到所有 Public(不是 B)能连接到它的途径,检查 A 是否有一个公共 IP 能被 Public 识别或使用,如果没有检查 A 的路由器是否允许公开端口转发规则、是否在路由上有公共代表等等。B 也做了以上同样的事。
此外,A 和 B 还会收集自身所支持的加密方式、安全参数、视频编解码器等等大量的信息,注意这些信息还没有被送到对端,在这个阶段只是广泛地收集。所有这些信息,构成了“SDP”。
接下来,A 和 B 会通过其他方式(可以是 WhatsApp、QR、Tweet、WebSockets、HTTP Fetch…)发出会话信息,这种方式具体是什么 WebRTC 并不关心,只要能从 A 到 B(B 到 A)就可以。
这种工作方式表面上是有些“愚蠢的”,部分人可能会认为“既然我已经有了 A 和 B 之间通信的线路,那还要 WebRTC 做什么呢?”但认真思考一下就可以发现,WebRTC 只要首次通信双方交换了 SDP,后面就会实现真正的 P2P 通信,不再需要 WhatsApp、QR 等等中间途径,不会有比这更快的通信路径。因此最终 A 通过最优路径连接到了 B,这就是 WebRTC 的工作流程。
更详细的阐释这个例子如下:
如上图所示,假设 A 找到了 A1、A2、A3 三种方式可以访问它,同时还找到了安全参数、媒体选项等信息。同时,B 也做了一样的工作。接下来,他们通过一些方式(例如 WhatsApp)交换了以上信息。然后 A 找到 B2 是可用的最佳路径,而 B 也发现 A1 是可用的最佳路径,那么二者将通过这条路径直接连接彼此。本质上 WebRTC 就是这样工作的。
2. WebRTC 揭秘
接下来我们对 WebRTC 进行深入理解,对细节内容进行讲述。首先了解 NAT 的细节,学习 WebRTC 是如何进行正确的网络地址转换;其次了解为什么我们需要 STUN 和 TURN;此外还会介绍 ICE、SDP 以及信令交换的相关内容。
2.1 Network Address Translation: NAT
如果你有一个公开的 Public IP 地址,连接过程将不会有什么问题。因为你会像 Web 服务器一样一直监听端口,把端口和 IP 都提供给对方后,你和它就可以直接进行连接了。但在大多数情况下,用户都是隐藏在公共网络之后的,无法直接连接。如下图所示的示例中,路由器有一个 Public IP 5.5.5.5,也有一个 Private IP 10.0.0.1(也被称为 gateway),你的机器只有一个 Private IP 10.0.0.2,但你想要访问 IP 为 4.4.4.4:80 的机器,要如何实现呢?
首先你的机器会构建一个数据包,声明想向 4.4.4.4:80 发出 GET 请求,10.0.0.2 是源 IP 地址。接下来,你的机器会通过子网掩码判断是否可以直接与 4.4.4.4:80 进行连接,运算结果会显示 4.4.4.4:80 并不在你所在的子网中,因此无法直接进行通信。所以下一步就需要将请求发送给路由器,借助 gateway 进行通信。路由器会替换源 IP 地址和端口为 Public IP 和一个随机端口,但在此之前会创建 NAT 表,来记录三者之间的对应关系。这样对端就能收到你的GET请求,并进行后续处理了。
在这之后,服务器 4.4.4.4:80 将向你的机器发送回复,工作原理和上述相同,根据 NAT 表查询对应地址完成通信。
NAT 的转换方式主要有以下几种,在默认情况下,WebRTC 可以支持前三种 NAT 方式,对最后一种并不友好。实际上 90% 以上的通信就是通过前三种方式完成的,最后一种作者个人认为没有使用的价值。
- 一对一 NAT(完全圆锥型 NAT):One to One NAT(Full-cone NAT) 路由器上要发送到外部 IP:port 的数据包总是可以映射到内部 IP:port ,无一例外。举例说明,所有发送到 5.5.5.5:3333 的数据包总是会被自动转发到 10.0.0.2:8992,无论这个包是来自 4.4.4.4:80 或者其他任何地址。
- IP 受限型 NAT:Address restricted NAT 出于安全考虑,部分路由器会地址限制,考虑之前是否与该地址进行过通信。即路由器上要发送到外部 IP:port 的数据包可以映射到内部 IP:port,前提是数据包的源地址与 NAT 表相符,无所谓端口是什么。举例说明,发送到 5.5.5.5:3333 的数据包中,只有源 IP 是 4.4.4.4 或其他表中有过记录的 IP 才会被自动转发到 10.0.0.2:8992,即使这个 IP 之前并不是和 3333 端口进行的通信。
- 端口受限型 NAT:Port restricted NAT 与前者相比,增加了端口限制,即路由器上要发送到外部 IP:port 的数据包可以映射到内部 IP:port,前提是数据包的源 IP 和 Port 都要与 NAT 表相符。举例说明,发送到 5.5.5.5:3333 的数据包中,只有来自 4.4.4.4:80 或其他表中有过记录的 IP:Port 才会被自动转发到 10.0.0.2:8992,即使这个 IP:Port 之前并不是和 3333 端口进行的通信。
- 对称 NAT:Symmetric NAT 该方式是限制最多的一种,即必须匹配完整的 IP:port,区别在于发送到 5.5.5.5:3333 的数据包中,只有来自 4.4.4.4:80 的才会被自动转发到 10.0.0.2:8992,其他的包均无法通过。这种方式无法在 WebRTC 中使用,因为 WebRTC 需要 STUN 服务器。一旦 STUN 服务器建立了一个 Public 代表,Symmetric NAT 要求只能与一个特定的对端通信,这种限制不适合 WebRTC。
2.2 Session Traversal Utilities for NAT:STUN
STUN 是可以赋予一个应用程序所需要的 Public IP 和 Port,适用于 Full-cone、Address restricted 和 Port restricted NAT,无法用于 Symmetric NAT。STUN 服务器通常在 3478 端口上运行,TLS 端口为 5349。STUN 是非常轻量级的,用户可以使用 docker 建立一个 STUN 服务器。STUN 服务器的目的就是让用户找到自己的 Public 表示,并通过这个 Public 表示与其他用户进行通信。如果我们使用的是像大约 1996 年或 2000 年早期时那样的 Public IP 地址,通信也将非常简单。但就现在而言,我们必须使用 STUN 服务器。STUN 服务器的工作流程如下图所示:
首先创建一个数据包进行 STUN 请求,STUN 服务器的地址为 9.9.9.9:3478,同样在路由器创建了 NAT 表并进行了地址转换,然后数据包被送到了 STUN 服务器。
服务器收到请求后,为 10.0.0.2 的机器构建了一个 Public 表示 5.5.5.5:3333,并把这个信息打包进一个数据包进行反馈。
上述是一个 STUN 请求的详细过程,以下图为例 STUN 在整个通信过程中进行了以下工作:首先给予 10.0.0.2 的机器一个 Public 表示 5.5.5.5:3333,同时给予 192.168.1.2 的机器一个 Public 表示 7.7.7.7:4444。随后二者都使用获得的 Public 表示进行连接。
值得注意的是,这二者之前并没有进行过通信。如果是 Full-cone NAT,那么没有问题可以连接;如果是 Address restricted NAT,第一个请求连接的请求将会失败。在这种情况下,用户需要通过服务器建立至少一个通信请求,先让两个地址都能保存在两端的路由器中,这样再次通过 Public 表示进行连接请求时就能找到匹配的地址,继而可以完成连接。Port restricted NAT 工作原理与之类似。
2.3 Traversal Using Relays around NAT: TURN
在应用 Symmetric NAT 的情况下,必须使用 TURN。所有的通信内容都要经过 TURN 服务器的转发,所以 TURN 服务器的维护成本比较高,这也是为什么几乎没有人免费提供这种服务器供用户使用。下图是一个 TURN 服务器工作流程的示例,二者之间并不是直接的 P2P 通信,所有的信息都经过了 TURN 服务器进行转发。
2.4 Interactive Connectivity Establishment: ICE
在建立了很多 STUN 和 TURN 服务器后,从 A 到 B 之间的路径有了非常多的选择,为了更好的处理这些路径,人们提出了 ICE。ICE 会收集所有可用的通信路径作为“候选人”(ICE Candidates),有可能是本地 IP 地址、STUN 和 TURN 服务器提供的地址等等。收集到的所有地址都将放入 SDP 中,再送到对端,对端通过解析 SDP 来了解我方提供的重要信息。因此,ICE 是 WebRTC 中非常关键的组成部分。
2.5 Session Description Protocol: SDP
SDP 是一种用于表述 ICE Candidates 的格式,它描述了网络选项、媒体选项、安全选项和其他很多信息,开发者甚至可以自定义 SDP 内容。实际上 SDP 并不是一种协议,只是一种数据格式,但 SDP 是 WebRTC 中最重要的几个概念之一。它的设计目的是将用户产生的 SDP 送至其他端,送的方式并不关心。
2.6 信令交换:Signaling
Signaling 过程是将用户产生的 SDP 通过某种方式传递给想要通信的那方,如上所述,以何种方式传递并不重要。很多人通过 Websockets 或者 socket io 来传递 SDP 信息,这个过程就是 Signal SDP。尽管要找到所有的 ICE candidate 是耗费时间的,但一旦完成了这个过程,下一步就是创建一个 SDP,进而生成一个 QR code 并把 QR code 公布到 twitter 上,其他人扫描了这个二维码就可以获取相应的 SDP。这个过程是通过 twitter、QR code、Whatsapp、WebSockets、还是 HTTP 请求都不重要,因为实际上就是将一个长字符串传递给其他人罢了。简而言之,Signaling 就是将 SDP 信息传递给另外一方。
工作流程总结
- A 想要和B建立连接;
- A 创建了一个 offer,它寻找所有的 ICE candidate、安全选项、音视频选项等并创建 SDP,简单来说这个 offer 就是 SDP;
- A 将 SDP 信令传递给 B(Signaling);
- B 根据 A 的 offer 进行设置,并创建应答(answer);
- B 将 Answer 信令传递给 A(Signaling);
- 连接建立。
3. Demo
作者详细讲述了一个 Demo 程序的编写,该程序可以:
- 在两个浏览器间进行通信(浏览器 A 和浏览器 B);
- A 创建一个 offer(SDP),并设置它为本地描述;
- B 接收一个 offer 并设置它为远端描述;
- B 创建一个 answer 并设置它为本地描述,并将其传递给 A;
- A 接收 answer 并设置它为远端描述;
- 建立连接、建立数据通道、交换数据。
源码:https://github.com/hnasr/javascript_playground/tree/master/webrtc
4. WebRTC的优缺点
1. 优点
- P2P 通信是非常棒的,对于高带宽内容可以有降低的延迟。P2P 是最快的路径,不需要经过其他的第三方进行通信。即使通互联网传输要经过大量的路由器,但如果内容已经被加密了所有的路由器都不会查看内容,它们会直接传递数据包,所以 P2P 是非常好的通信方式。对于高带宽内容,它们通过 UDP 直接被“送入”和“推出”,通过 P2P UDP 传递这些内容(特别是视频内容),用户将收获最好的性能。
- 标准可用的 API WebRTC 有一套非常标准、非常优雅的 API,可以直接在浏览器中应用,不需要安装其他的包、也不需要用多余的开发工具。
2. 缺点
- 需要维护 STUN 和 TURN 服务器 在某些情况下 P2P 不能工作,你仍需要一个 TURN 服务器。但维护 STUN 和 TURN 服务器需要耗费大量的人力物力,特别是 TURN 服务器。因为你首先要花钱维护一个 Public IP,并且必须维护这个服务器使其可以正常启动和运行。作者个人认为与其花费这种代价,不如自己建立一个拥有全部控制权的服务器,进行反向代理。
- 在参与者过多的情况下,P2P 会崩溃 假设有 100 个人想要相互交流,你会创建 P2P 连接吗?那会是几百乘几百的连接量,因为每个人都需要连接到其他任何一个用户,这将是非常大规模的。但如果你有一个集中式服务器,每个用户只需要和这个服务器建立一个连接,你可以通过这个服务器控制所有的流量,这明显是一种更好的方式。所以 WebRTC 有时候无法用在游戏上,你没办法利用 WebRTC 来创建一个多用户游戏,当然 3 个用户是可以的,但几百个用户作者认为是无法实现的。
5. 扩展内容
5.1 Media API
getUserMedia 函数可以用于获取麦克风和摄像头,进而获得一个流(stream),这个流的内容会通过RTCPConnection.addTrack(stream)送入 RTC 连接中。理论上你可以用数据通道传递任何类型的数据,但如果你想要传递媒体信息就要用到 stream,这些数据的传递将使用不同的协议。
更多内容可参考:https://www.html5rocks.com/en/tutorials/webrtc/basics/
5.2 onIceCandidate 和 addIceCandidate
这两个函数可用于在新的 Candidate 加入或离开时维护连接。用户每次从系统获取一个 ICE Candidate 时,onIceCandidate 函数就会被调用。onIceCandidate 函数将告知用户“在 SDP 已经被创建后,又有了新的 Candidate”。新的 Candidate 将被告知对端,告知的方式可以是 Signaling,也可以直接通过同一个 SDP 连接。对端通过 addIceCandidate 函数将新的 Candidate 加入 SDP。
5.3 自定义 TURN 和 STUN 服务器
在创建 RTCP 连接时,可以选择传递配置信息,下图为一个配置信息示例。基本上用户可以自定义 ICE 服务器,其中有很多可选项。
此外,有一个开源库也可以帮助大家创建属于自己的 TURN 服务器,地址:https://github.com/coturn/coturn
5.4 公共 STUN 服务器
作者给出了部分 Google 提供的公共服务器,可供开发人员参考:
- stun1.l.google.com:19302
- stun2.l.google.com:19302
- stun3.l.google.com:19302
- stun4.l.google.com:19302
- stun.stunprotocol.org:3478
附上演讲视频:
http://mpvideo.qpic.cn/0bc33qaceaaa2aahncygvrrfbxgdeloaaiqa.f10002.mp4?dis_k=e6c887c1460e5f17cc844ece0baea03b&dis_t=1645153755&vid=wxv_2264750176378765323&format_id=10002&support_redirect=0&mmversion=false