网络中继
当遇到 NAT 之间无法打通的情况时,WebRTC
会使用 TURN 协议通过中转的方式实现端与端之间的通信。接下来,我们看一下 WebRTC
是如何通过 TURN 协议进行数据中转的。
TURN协议中转数据
TURN 协议底层依赖于 STUN 协议。关于 STUN 协议的具体内容这里就不做介绍了,如果读者对它感兴趣的话,可以自行查阅 RFC3489
和 RFC5389
的内容。
TURN 协议采用了典型的客户端/服务器模式,其服务器端称为 TurnServer
,客用户端称为 TurnClient
。TurnClient
与 TurnServer
之间通过信令控制数据流的发送,整个过程如图 6.13 所示。
主机X(TurnClient
)首先向 TurnServer
的 3478
端口发送 Allocate
指令,也就是图 6.13 中的第❶步。TurnServer
收到该消息后,在 TURN
服务端分配一个与 TurnClient
相对应的 relay
地址,任何发向 relay
地址的数据都会被转发到 TurnClient
端。
主机 A 和主机 B 并不是 TurnClient
,它们被称为 Peer
端。Peer
端可以使用 UDP 向 TurnServer
的 Relay
地址发送数据,TurnServer
根据映射关系,将 relay
地址收到的数据转给对应的 TurnClient
,从而实现 TurnClient
与 Peer
之间的互通,这就是图 6.13 中的第❷步和第❸步。
这里需要注意的是,TurnClient
与 TurnServer
之间的传输协议既可以是 UDP,也可以是 TCP,而在 TurnServer
上分配的 relay
地址使用的都是 UDP。
在图 6.13 中可以看到,主机 X 可以同时收到主机 A 和主机 B 发送的数据,但主机 X 发送的数据是如何控制哪些是给主机 A 的,哪些又是给主机 B 的呢?这就要说到 TURN 协议中另外一个指令 Send indication 了。该指令包括两个属性:XOR-PEER-ADDRESS 和 DATA。其中 XOR-PEER-ADDRESS 属性用于指定向哪个主机转发数据,DATA 属性指明数据的具体内容。
实际上,TURN 协议对于端对端传输数据提供了两种方法。方法一就是上面说的 Send indication
指令,当 TurnClient
向某个 Peer
发数据时就要使用它。相反,当 Peer
通过 TurnServer
向 TurnClient
转发数据时,使用 Data indication
指令,指明向哪个 TurnClient
转发数据。方法二是使用 tunnel
机制。使用 tunnel
机制的好处是不用再像使用 Send/Data indication
指令一样,每次都要指定数据发往的地址,只需要在开始发送数据之前,发送 ChannelBind
指令将 channel number
与目标地址绑定一次即可,后面统一使用 channel number
就可以找到发往的目的地。以上就是 TURN 协议的核心内容。

WebRTC使用TURN协议
了解了 TURN 协议的基本工作原理后,接下来看一下 WebRTC
是如何使用 TURN 协议进行数据转发的。WebRTC
收集到的 Relay
类型的 Candidate
,指的就是通过 TURN 协议的 Allocation
指令分配的地址。
正如在 6.4.1 节中所介绍的,通信的双方在使用 TURN 服务器转发数据时,一方作为 TurnClient
,而另一方作为 Peer
来互通数据。实现上,使用 TURN 服务器时不必非要采用这种模式,也可以让通信的双方都是 TurnClient
,这样的实现方式反而更简单。
WebRTC
使用的就是这种方案,如图 6.14 所示。主机 A 和主机 B 同时兼有两个角色:角色一,它们都是 TurnClient
,因此都可以使用 TURN 协议与 TurnServer
建立联系;角色二,它们是彼此的 Peer
端,所以可以向对端的 Relay
地址发送数据,从而让 TurnServer
将数据中转给对端。
下面详细分析一下图 6.14 。主机 A 向 TurnServer
发送 Allocate
指令,要求分配一个 Relay
地址,TurnServer
收到指令后分配地址 RelayA
。现在,只要有主机知道 RelayA
的地址,并向该地址发送数据,TurnServer
就会将 RelayA
收到的数据转发给主机 A;同样地,主机 B 也向 TurnServer
发送 Allocate
指令,得到 TurnServer
分配的 RelayB
。
图6.14 中的主机 A 与主机 B 都是 WebRTC
终端,因此在媒体协商时会通过信令交换彼此的 Candidate
,Relay
类型的 Candidate
也会在此时被交换。当 WebRTC
被试用 host
、srflx
类型的 Candidate
但无法连接成功时,就会尝试使用 Relay
类型的 Candidate
。

因为主机 A 拿到了主机 B 的 Relay
类型的 Candidate
,即 RelayB
,所以主机 A 可以直接将音视频数据发向 RelayB
。TurnServer
从 RelayB
接收到数据后,会将数据打包成 TURN
消息,经 3478 端口发往主机 A。主机 A 收到数据后,再利用 TurnClient
模块将数据从 TURN
消息中取出,交给其他模块做进一步处理。同理,主机 B 与主机 A 的操作流程是一样的。TurnServer
从 RelayA
收到数据后,将其打包成 TURN
消息,也要经过 3478 端口转发给主机 B。
通过上面的描述可以看到,所有发往 TurnClient
的数据都要经过端口 3478,所以 3478 是一个多路复用的端口,同时它也是 TURN
协议的默认端口。另外,各端的 Relay
端口则是在 TurnServer
收到 Allocation
指令时才随机分配的。
STUN/TURN服务器的安装与部署
在公网上搭建一套 STUN/TURN 服务并不难。需要有一台云主机。目前比较流行的 STUN/TURN 服务器是 Google 开源的 coturn
服务器,使用它搭建 STUN/TURN 服务非常方便。下面看一下部署它的基本步骤:
-
获取
coturn
源码:git clone https:// github.com/coturn/coturn.git
-
编译安装:
cd coturn ./ config ure --prefix =/usr/local/coturn sudo make -j 4 && make install
-
配置
coturn
。网上有很多关于coturn
的配置文章,内容很复杂而且错误百出,其实只要使用coturn
的默认设置并修改以下几个配置项即可,如下所示:// 指定侦听的端口 listening-port =3478 // 指定云主机的公网IP 地址 external-ip=xxx.xxx.xxx.xxx // 访问STUN/TURN 服务的用户名和密码 user=aaaaaa:bbbbbb
-
启动 STUN/TURN 服务:
cd /usr/local/coturn/bin turnserver -c ../ etc/turnserver.conf
-
测试 STURN/TURN 服务。打开
trickle-ice
测试工具,按要求输入 STUN/TURN 地址、用户和密码后就可以探测 STUN/TURN 服务运行是否正常了,如下所示:STUN or TURN URI 的值为: turn:stun.xxx.cn 用户名为: aaaaaa 密码为: bbbbbb
STUN/TURN 部署好后,就可以使用它转发多媒体数据了,不用担心通信双方因 NAT 或防火墙等原因而无法通信的问题。