网络中继

当遇到 NAT 之间无法打通的情况时,WebRTC 会使用 TURN 协议通过中转的方式实现端与端之间的通信。接下来,我们看一下 WebRTC 是如何通过 TURN 协议进行数据中转的。

TURN协议中转数据

TURN 协议底层依赖于 STUN 协议。关于 STUN 协议的具体内容这里就不做介绍了,如果读者对它感兴趣的话,可以自行查阅 RFC3489RFC5389 的内容。

TURN 协议采用了典型的客户端/服务器模式,其服务器端称为 TurnServer,客用户端称为 TurnClientTurnClientTurnServer 之间通过信令控制数据流的发送,整个过程如图 6.13 所示。

主机X(TurnClient)首先向 TurnServer3478 端口发送 Allocate 指令,也就是图 6.13 中的第❶步。TurnServer 收到该消息后,在 TURN 服务端分配一个与 TurnClient 相对应的 relay 地址,任何发向 relay 地址的数据都会被转发到 TurnClient 端。

主机 A 和主机 B 并不是 TurnClient,它们被称为 Peer 端。Peer 端可以使用 UDPTurnServerRelay 地址发送数据,TurnServer 根据映射关系,将 relay 地址收到的数据转给对应的 TurnClient,从而实现 TurnClientPeer 之间的互通,这就是图 6.13 中的第❷步和第❸步。

这里需要注意的是,TurnClientTurnServer 之间的传输协议既可以是 UDP,也可以是 TCP,而在 TurnServer 上分配的 relay 地址使用的都是 UDP

在图 6.13 中可以看到,主机 X 可以同时收到主机 A 和主机 B 发送的数据,但主机 X 发送的数据是如何控制哪些是给主机 A 的,哪些又是给主机 B 的呢?这就要说到 TURN 协议中另外一个指令 Send indication 了。该指令包括两个属性:XOR-PEER-ADDRESSDATA。其中 XOR-PEER-ADDRESS 属性用于指定向哪个主机转发数据,DATA 属性指明数据的具体内容。

实际上,TURN 协议对于端对端传输数据提供了两种方法。方法一就是上面说的 Send indication 指令,当 TurnClient 向某个 Peer 发数据时就要使用它。相反,当 Peer 通过 TurnServerTurnClient 转发数据时,使用 Data indication 指令,指明向哪个 TurnClient 转发数据。方法二是使用 tunnel 机制。使用 tunnel 机制的好处是不用再像使用 Send/Data indication 指令一样,每次都要指定数据发往的地址,只需要在开始发送数据之前,发送 ChannelBind 指令将 channel number 与目标地址绑定一次即可,后面统一使用 channel number 就可以找到发往的目的地。以上就是 TURN 协议的核心内容。

image 2025 02 23 12 38 09 832
Figure 1. 图6.13 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 终端,因此在媒体协商时会通过信令交换彼此的 CandidateRelay 类型的 Candidate 也会在此时被交换。当 WebRTC 被试用 hostsrflx 类型的 Candidate 但无法连接成功时,就会尝试使用 Relay 类型的 Candidate

image 2025 02 23 12 39 00 578
Figure 2. 图6.14 WebRTC如何使用TURN协议

因为主机 A 拿到了主机 B 的 Relay 类型的 Candidate,即 RelayB,所以主机 A 可以直接将音视频数据发向 RelayBTurnServerRelayB 接收到数据后,会将数据打包成 TURN 消息,经 3478 端口发往主机 A。主机 A 收到数据后,再利用 TurnClient 模块将数据从 TURN 消息中取出,交给其他模块做进一步处理。同理,主机 B 与主机 A 的操作流程是一样的。TurnServerRelayA 收到数据后,将其打包成 TURN 消息,也要经过 3478 端口转发给主机 B。

通过上面的描述可以看到,所有发往 TurnClient 的数据都要经过端口 3478,所以 3478 是一个多路复用的端口,同时它也是 TURN 协议的默认端口。另外,各端的 Relay 端口则是在 TurnServer 收到 Allocation 指令时才随机分配的。

STUN/TURN服务器的安装与部署

在公网上搭建一套 STUN/TURN 服务并不难。需要有一台云主机。目前比较流行的 STUN/TURN 服务器是 Google 开源的 coturn 服务器,使用它搭建 STUN/TURN 服务非常方便。下面看一下部署它的基本步骤:

  1. 获取 coturn 源码:

    git clone https:// github.com/coturn/coturn.git
  2. 编译安装:

    cd coturn
    ./ config
    ure --prefix =/usr/local/coturn
    sudo make -j 4 && make install
  3. 配置 coturn。网上有很多关于 coturn 的配置文章,内容很复杂而且错误百出,其实只要使用 coturn 的默认设置并修改以下几个配置项即可,如下所示:

    // 指定侦听的端口
    listening-port =3478
    // 指定云主机的公网IP 地址
    external-ip=xxx.xxx.xxx.xxx
    // 访问STUN/TURN 服务的用户名和密码
    user=aaaaaa:bbbbbb
  4. 启动 STUN/TURN 服务:

    cd /usr/local/coturn/bin
    turnserver -c ../ etc/turnserver.conf
  5. 测试 STURN/TURN 服务。打开 trickle-ice 测试工具,按要求输入 STUN/TURN 地址、用户和密码后就可以探测 STUN/TURN 服务运行是否正常了,如下所示:

    STUN or TURN URI 的值为: turn:stun.xxx.cn
    用户名为: aaaaaa
    密码为: bbbbbb

STUN/TURN 部署好后,就可以使用它转发多媒体数据了,不用担心通信双方因 NAT 或防火墙等原因而无法通信的问题。