CategoryResourceRepost/极客时间专栏/从0打造音视频直播系统/WebRTC 1对1通话/09 | 让我们揭开WebRTC建立连接的神秘面纱.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

178 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="09 | 让我们揭开WebRTC建立连接的神秘面纱" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b7/b8/b7ff922d81efdcaef3bec6f31bc365b8.mp3"></audio>
在上一篇[《08 | 有话好商量,论媒体协商》](https://time.geekbang.org/column/article/111675)文章中我向你介绍了WebRTC进行通信时是如何进行媒体协商的以及媒体协商的目的是什么。
在媒体协商过程中如果双方能达成一致也就是商量好了使用什么编解码器确认了使用什么传输协议那么接下来WebRTC就要建立连接开始传输音视频数据了。
WebRTC之间建立连接的过程是非常复杂的。之所以复杂主要的原因在于它既要考虑传输的**高效性**,又要保证端与端之间的**连通率**。
换句话说,当同时存在多个有效连接时,它首先选择传输质量最好的线路,如能用内网连通就不用公网。另外,如果尝试了很多线路都连通不了,那么它还会使用服务端中继的方式让双方连通,总之,是“想尽办法,用尽手段”让双方连通。
对于**传输的效率与连通率**这一点既是WebRTC的目标也是 WebRTC 建立连接的基本策略。下面我们就来具体看一下 WebRTC 是如何达到这个目标的吧!
## 在WebRTC 处理过程中的位置
下面这张图清晰地表达了本文所讲的内容在整个WebRTC处理过程中的位置。
<img src="https://static001.geekbang.org/resource/image/fb/ff/fbce1bba75073ab721cca628b0a61cff.png" alt="">
图中的红色部分——连接的创建、STUN/TURN以及 NAT 穿越,就是我们本文要讲的主要内容。
## 连接建立的基本原则
接下来,我将通过两个具体的场景,向你介绍一下 WebRTC 建立连接的基本原则。不过在讲解之前,我们先设置一些假设条件,这样会更有利于我们下面的描述:
- 通信的双方我们称为 A 和 B
- A 为呼叫方B 为被呼叫方;
- C 为中继服务器,也称为 relay 服务器或 TURN服务器。
### 1. 场景一:双方处于同一网段内
A 与 B 进行通信假设它们现在处于同一个办公区的同一个网段内。在这种情况下A 与 B 有两种连通路径:
- 一种是双方通过内网直接进行连接;
- 另一种是通过公网,也就是通过公司的网关,从公网绕一圈后再进入公司实现双方的通信。
相较而言,显然第一种连接路径是最好的。 A 与 B 在内网连接就好了,谁会舍近求远呢?
但现实却并非如此简单,要想让 A 与 B 直接在内网连接,首先要解决的问题是: A 与 B 如何才能知道它们是在同一个网段内呢?
这个问题还真不好回答也正是由于这个问题不太好解决所以现在有很多通信类产品在双方通信时无论是否在同一个内网它们都统一走了公网。不过WebRTC很好的解决了这个问题后面我们可以看一下它是如何解决这个问题的。
### 2. 场景二:双方处于不同点
A 与 B 进行通信,它们分别在不同的地点,比如一个在北京,一个在上海,此时 A 与 B 通信必须走公网。但走公网也有两条路径:
- 一是通过 P2P 的方式双方直接建立连接;
- 二是通过中继服务器进行中转即A 与 B 都先与 C 建立连接,当 A 向 B 发消息时, A先将数据发给 C然后 C 再转发给 B同理 B 向 A 发消息时B 先将消息发给C然后C再转给A。
对于这两条路径你该如何选择呢?对于 WebRTC 来讲,它认为**通过中继的方式会增加 A 与 B 之间传输的时长,所以它优先使用 P2P 方式;如果 P2P 方式不通,才会使用中继的方式**。
通过上面两个场景的描述,我想你应该已经了解到 WebRTC 为了实现端与端之间连接的建立,做了非常多的工作。下面我们就来一起看看 WebRTC 建立连接的具体过程吧!
## 什么是Candidate
在讲解 WebRTC 建立连接的过程之前,你有必要先了解一个基本概念,即 **ICE Candidate ICE 候选者)**。它表示 WebRTC 与远端通信时使用的协议、IP地址和端口一般由以下字段组成
- 本地IP地址
- 本地端口号
- 候选者类型,包括 host、srflx和relay
- 优先级
- 传输协议
- 访问服务的用户名
- ……
如果用一个结构表示,那么它就如下面所示的样子:
```
{
IP: xxx.xxx.xxx.xxx,
port: number,
type: host/srflx/relay,
priority: number,
protocol: UDP/TCP,
usernameFragment: string
...
}
```
其中,候选者类型中的 host 表示**本机候选者**srflx表示内网主机映射的**外网的地址和端口**relay表示**中继候选者**。
当WebRTC通信双方彼此要进行连接时每一端都会提供许多候选者比如你的主机有两块网卡那么每块网卡的不同端口都是一个候选者。
WebRTC会按照上面描述的格式对候选者进行排序然后按优先级从高到低的顺序进行连通性测试当连通性测试成功后通信的双方就建立起了连接。
在众多候选者中,**host 类型的候选者优先级是最高的**。在 WebRTC 中,首先对 host 类型的候选者进行连通性检测,如果它们之间可以互通,则直接建立连接。其实,**host 类型之间的连通性检测就是内网之间的连通性检测**。WebRTC就是通过这种方式巧妙地解决了大家认为很困难的问题。
同样的道理,如果 host 类型候选者之间无法建立连接,那么 WebRTC 则会尝试**次优先级的候选者,即 srflx 类型的候选者**。也就是尝试让通信双方直接通过 P2P 进行连接,如果连接成功就使用 **P2P 传输数据**;如果失败,就最后尝试使用 relay 方式建立连接。
通过上面的描述,你是不是觉得 WebRTC 在这里的设计相当精妙呢?当然在 WebRTC 看来以上这些只不过是一些“皮毛”在下一篇关于NAT穿越原理一文中你还会看WebRTC在 NAT 穿越上的精彩处理。
## 收集 Candidate
了解了什么是 Candidate 之后,接下来,我们再来看一下端对端的连接是如何建立的吧。
实际上,端对端的建立更主要的工作是**Candidate的收集**。WebRTC 将 Candidate 分为三种类型:
- host类型即本机内网的 IP 和端口;
- srflx类型, 即本机NAT映射后的外网的 IP 和端口;
- relay类型即中继服务器的 IP 和端口。
其中host 类型优先级最高srflx 次之relay最低前面我们已经说明过了
在以上三种Candidate类型中**host 类型的 Candidate 是最容易收集的**,因为它们都是本机的 IP 地址和端口。对于 host 类型的 Candidate 这里就不做过多讲解了,下面我们主要讲解一下 srflx 和 relay 这两种类型的 Candidate的收集。
### 1. STUN协议
srflx 类型的 Candidate 实际上就是内网地址和端口**经 NAT 映射**后的外网地址和端口。如下图所示:
<img src="https://static001.geekbang.org/resource/image/b0/ca/b04a0cd49c3e93e1cc536566404affca.png" alt="">
你应该知道,如果主机没有公网地址,是无论如何都无法访问公网上的资源的。例如你要通过百度搜索一些信息,如果你的主机没有公网地址的话,百度搜索到的结果怎么传给你呢?
而一般情况下主机都只有内网IP和端口那它是如何访问外网资源的呢实际上在内网的网关上都有NAT (Net Address Transport) 功能,**NAT 的作用就是进行内外网的地址转换**。这样当你要访问公网上的资源时NAT首先会将该主机的内网地址转换成外网地址然后才会将请求发送给要访问的服务器服务器处理好后将结果返回给主机的公网地址和端口再通过 NAT 最终中转给内网的主机。
知道了上面的原理你要想让内网主机获得它的外网IP地址也就好办了只需要在公网上架设一台服务器并向这台服务器发个请求说 “Hi伙计你看我是谁”对方回 “你不是那xxxx吗”这样你就可以知道自己的公网 IP 了,是不是很简单?
实际上,上面的描述已经被定义成了一套规范,即 RFC5389 ,也就是 **STUN 协议**,我们只要**遵守这个协议就可以拿到自己的公网 IP 了**。
这里我们举个例子,看看通过 STUN 协议主机是如何获取到自己的外网IP地址和端口的。
- 首先在外网搭建一个 STUN 服务器,现在比较流行的 STUN 服务器是 CoTURN你可以到 GitHub 上自己下载源码编译安装。
- 当 STUN 服务器安装好后从内网主机发送一个binding request的STUN 消息到 STUN 服务器。
- STUN 服务器收到该请求后,会将请求的 IP地址和端口填充到binding response消息中然后顺原路将该消息返回给内网主机。此时收到binding response消息的内网主机就可以解析 binding response消息了并可以从中得到自己的外网IP和端口。
### 2. TURN协议
>
这里需要说明一点relay服务是通过TURN协议实现的。所以我们经常说的relay服务器或TURN服务器它们是同一个意思都是指中继服务器。
咱们言归正转,知道了内网主机如何通过 STUN 协议获取到srflx类型的候选者后那么中继类型候选者即 relay型的 Candidate 又是如何获取的呢?下面我们就来看一下。
首先你要清楚,**relay型候选者的优先级与其他类型相比是最低的**但在其他候选者都无法连通的情况下relay候选者就成了最好的选择。因为**它的连通率是所有候选者中连通率最高的**。
其实relay型候选者的获取也是通过 STUN 协议完成的只不过它使用的STUN消息类型与获取 srflx 型候选者的 STUN 消息的类型不一样而已。
RFC5766的 TURN 协议描述了如何获取 relay服务器即TURN 服务器)的 Candidate过程。其中最主要的是 Allocation指令。通过向 TURN 服务器发送 Allocation 指令relay服务就会在服务器端分配一个新的relay端口用于中转UDP数据报。
不过这里我只是简要描述了下,如果你对这块感兴趣的话,可以直接查看 RFC5766 以了解更多的细节。
## NAT打洞/P2P穿越
当收集到 Candidate 后WebRTC 就开始按优先级顺序进行连通性检测了。它首先会判断两台主机是否处于同一个局域网内,如果双方确实是在同一局域网内,那么就直接在它们之间建立一条连接。
但如果两台主机不在同一个内网WebRTC将尝试**NAT打洞即 P2P 穿越**。在WebRTC中NAT打洞是极其复杂的过程它首先需要对 NAT 类型做判断,检测出其类型后,才能判断出是否可以打洞成功,只有存在打洞成功的可能性时才会真正尝试打洞。
WebRTC将 NAT 分类为 4 种类型,分别是:
- 完全锥型 NAT
- IP 限制型 NAT
- 端口限制型 NAT
- 对称型 NAT
而每种不同类型的 NAT 的详细介绍我们将在下一篇关于NAT穿越原理一文中进行讲解现在你只要知道 NAT 分这4种类型就好了。另外需要记住的是对称型NAT 与对称型 NAT 是无法进行P2P穿越的而对称型NAT与端口限制型NAT也是无法进行 P2P 连接的。
## ICE
了解了上面的知识后,你再来看 ICE 就比较简单了。其实 ICE 就是上面所讲的获取各种类型 Candidate的过程也就是**在本机收集所有的 host 类型的 Candidate通过 STUN 协议收集 srflx 类型的 Candidate使用 TURN 协议收集 relay类型的 Candidate**。
因此,有人说 ICE 就是包括了STUN、TURN 协议的一套框架,从某种意义来说,这样描述也并不无道理。
## 小结
通过上面的讲解,我想你现在已经基本了解 WebRTC 端对端建立连接的基本过程。在WebRTC中它首先会尝试NAT穿越即尝试端到端直连。如果能够穿越成功那双方就通过直连的方式传输数据这是最高效的。但如果 NAT 穿越失败为了保障通信双方的连通性WebRTC会使用中继方式当然使用这种方式传输效率会低一些。
在整个过程中WebRTC使用**优先级**的方法去建立连接即局域网内的优先级最高其次是NAT穿越再次是通过中继服务器进行中转这样就巧妙地实现了“既要高效传输又能保证连通率”这个目标。
当然,即使 WebRTC 处理得这样好但还有不够完美的地方。举个例子对于同一级别多个Candidate的情况WebRTC 就无法从中选出哪个 Candidate 更优了,它现在的做法是,在同一级别的 Candidate 中,谁排在前面就先用谁进行连接。
## 思考时间
若你查阅相关资料一定会发现Candidate的类型是四种而不是三种多了一种 prflx 类型,那么 prflx 类型与 srflx 类型的区别是什么呢?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。