大家好,很高兴又见面了,我是"前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

1.前言
1.1 什么是 WebRTC?
下图展示了不同协议出现的时间线:

在计算机网络中,协议是一组规则,用于管理数据在设备之间的交换方式。 协议定义了通信的规则、语法、语义和同步以及可能的错误恢复方法,本文中讨论的 WebRTC 协议定义了应用层软件如何相互交互。
WebRTC(Web Real-Time Communication)也被称为网络实时通信,是由 Google、Mozilla 和其他公司推动的一个开源项目,它通过 Javascript API 实现无插件的实时通信,建立浏览器之间点对点(PEer-to-Peer)的连接。
1.2 WebRTC 的优点?
WebRTC 的优点可以归纳为以下几个方面:
- 开源、免费,开发者不需要承担高昂的专利费用
- 基于浏览器,不需要安装插件,只要调用 API 就可以实现音视频互动
- 被纳入了 HTML5 标准,主流浏览器全面支持 WebRTC
- 不仅支持 Web 之间的音视频通讯,还支持 Android 以及 IOS 端,由于该项目是开源的,我们也可以通过编译 C++代码,从而达到全平台的互通
在 WebRTC 诞生之前,开发实时音视频应用的成本是非常高,需要考虑的技术问题很多,如音视频的编解码,数据传输延时、丢包、网络抖动、回音处理和消除等,如果要兼容浏览器端的实时音视频通信,还需要额外安装插件。可喜的是,本文的主角 WebRTC 在 2021 年 1 月被 W3C 和 IETF 发布为正式标准,而且得到了大多数主流浏览器的支持。

WebRTC 项目的愿景:实时通信 web 化,让 WebRTC 成为互联网音视频实时通信的规范,让开发者基于此规范快速开发出安全、可靠的应用。WebRTC 必须在 HTTPS 环境下运行,你可以在https://apPR.tc/、https://snapdrop.net/体验WebRTC应用,或者在
https://nashaofu.github.io/webrtc-demo/,https://webrtc.github.io/samples/查看WebRTC示例。
2.了解SDP和Offer-Answer模型
2.1 什么是RTP/RTCP?
大多数人已经了解 TCP 和 UDP 等传输协议,当您要保证传输数据准确(例如:邮件)时,TCP 协议更好,而 UDP则更加偏向传输速度(例如:YOUTube 视频)。
实时传输协议 (Real-time Transport Protocol,RTP) 又是一种用于通过 IP 网络传送音频和视频的网络协议。 RTP 广泛用于涉及流媒体的通信和娱乐系统,例如电话、视频电话会议、电视服务等。 作为一种电信标准,WebRTC 正在使用 RTP 传输实时数据。
RTP 控制协议 (RTP Control Protocol ,即RTCP) 是实时传输协议 (RTP) 的兄弟协议。 RTCP 为 RTP 会话提供额外统计和控制信息。 使用 RTCP您可以获得有关数据传输成功的数据,例如“传输过程中发生了多少数据包丢失”、“数据包延迟是多少”或“视频通话的分辨率是多少”等等。
RTP 和 RTCP 数据包的传输发生在媒体通道上,而WebRTC 负责媒体通道上的媒体传输。作为应用程序开发人员,您的责任是管理信令通道。 所以你通常不知道这些概念,而且大多数时候你不需要它们,但在开始 SDP 和 Offer-Answer 模型之前有必要先了解下 RTP/RTCP 。
2.2 什么是SDP(Session Description Protocol)?
现实生活中,如果您想让人们联系到您,您会分享您的联系信息,比如电子邮件、电话号码、Instagram 帐户、家庭住址等。 分享此类信息的最简单方法是向他们提供您的名片,而为了能够找到对方, 互联网数字名片可能包含以下信息:
- 主叫方和被叫方IP 地址
- 支持哪些媒体类型(音频、视频、屏幕共享等)
- 当前启用或禁用了哪些媒体类型(视频开/关保持/取消保持等)
- 对方都支持哪些编解码器类型

在电信领域,称这种数字名片为会话描述协议 (SDP), SDP 包含对等点相互交谈所需的信息。WebRTC 也使用 SDP 作为通信标准来发起呼叫, SDP 只是一个可以被端点解析和操作的文本。
例如,如果用户想要保持通话,您可以通过将 SDP 作为应用程序进行操作来禁用/启用视频和音频流。 或者您的系统需要特定的视频编解码器,比方说 H.264,您可以删除除 H.264 之外的任何其他编解码器。
这就是 SDP 的强大之处,它很容易根据您的要求进行操作
下面是一个 SDP 示例,可能包含以下信息:
o=alice 2890844526 2890844526 IN IP4 10.48.1.2//O=表示呼叫的发起者、会话ID和发起者的IP地址t=0 0//t= 表示会话结束时间。如果为 0,则表示会话不受时间限制m=audio 49170 UDP/TLS/RTP/SAVPF 111 0// m=表示media line,是session中可以存在的媒体属性。 在这种情况下,它表示音频媒体线。 此行还包含将在会话中使用的传输协议 (UDP/TLS/RTP/SAVPF)。 最后,此行包含将在会话中使用的编解码器有效载荷编号 (111, 0)c=IN IP4 217.345.789.123// c=表示连接信息,比如你要调用的远程设备的IP地址a=sendrecva=rtpmap:111 opus/48000/2a=rtpmap:0 PCMU/8000//a= 表示属性线。 它定义会话和媒体行属性。 在第一行中,a=sendrcv 属性表示设备愿意发送和接收音频媒体,他还 可以有其他值,如 recvonly、sendonly 或 inactive用于不同的场景,如保持或视频关闭// Rtpmap 属性指示音频编解码器编号的映射。 在这种情况下,111 映射到具有 48,000 bps 带宽的 Opus,而 0 映射到具有 8,000 bps 带宽的 PCMU 编解码器, 标准 SDP 中可以有更多的属性行。m=video 51372 UDP/TLS/RTP/SAVPF 98 100//m= 也表示媒体行,在这种情况下,它表示视频媒体线。同样,它包含传输协议和编解码器有效负载编号。a=sendrecva=rtpmap:98 VP9/90000a=rtpmap:100 H264/90000//a= 表示属性行。 在第一行中,a=sendrcv 属性表示设备愿意发送和接收视频媒体//rtpmap 值,98 映射到具有 90,000 bps 带宽的 VP9 视频编解码器,100 映射到具有 90,000 bps 带宽的 H.264 视频编解码器
SDP的更多属性配置可以阅读文末资料,这里不再展开。
2.3 什么是Offer-Answer模型?
到目前为止,解释了“WebRTC 如何在媒体通道中传输数据?”(RTP/RTCP)和“如何在信令通道中根据需要指定会话属性?”(SDP)。 接下来回答“应用程序应该如何相互传输会话属性 (SDP)?”。
你可能有一张华丽的名片,但如果你不把它送给任何人,它就毫无用处, 此规则也适用于 SDP。 我们需要在对等点之间交换 SDP 以发起呼叫。 Offer-Answer 模型是在 WebRTC 中用作电信标准的 SDP 交换过程,交换方式由申请时决定。 应用程序可以通过 HTTP/HTTPS 请求、Web 套接字、推送通知等方式发送它。这完全取决于应用程序。

Offer-Answer模型顾名思义,在这个模型中有一个 Offerer 和一个 Answerer。 提供者是启动信令过程的人,例如开始拨出电话或发送通话事件的人,包括保持和关闭视频开关。 回答者是回答传入提议的人, 例如接听来电或向通话中事件发送合适的数据。
Offer-Answer 模型有 4 个基本步骤;
- Offerer 创建一个 Offer SDP 并将其发送到远程节点。
- 应答者收到提议者的 SDP,并自行设置。
- Answerer 创建一个 Answer SDP 并将其发送给 offerer
- 提供者收到应答者的 SDP,并自行设置。
之后,如果一切正常,呼叫开始。
2.4 WebRTC的Offer-Answer模型交换流程
下图显示了 WebRTC 上使用 Offer-Answer 模型的 SDP(Session Description Protocol,即会话描述协议) 交换过程:

接下来一一说明这些步骤:
- Peer-1 获取用户媒体,然后从 WebRTC 创建一个 PeerConnection 对象
- 创建PeerConnection后,应用程序需要调用WebRTC的createOffer接口
- WebRTC 创建一个Offer SDP ,并且可以根据需要操作 SDP
- 应用程序应将 Offer SDP 设置回 WebRTC
- 应用程序应向 Peer-2 发送Offer SDP
- Peer-2 上的应用程序收到Offer SDP, Peer-2 应该获取用户媒体并创建 PeerConnection 对象(如果目前尚未创建)
- Peer-2 上的应用程序将Offer SDP 设置给 WebRTC
- Peer-2 上的应用程序使用 WebRTC 的 createAnswer API 生成应答 SDP
- WebRTC 创建一个应答 SDP 并将其提供给应用程序,应用程序可以根据需要操作SDP
- 应用程序应将 Answer SDP 设置给 WebRTC
- Peer-2 上的应用程序应将应答 SDP 发送给 Peer-1
- Peer-1 上的应用程序设置应答 SDP
- 如果一切正常,RTP 媒体流将通过 WebRTC 在媒体通道上启动。
上面可能有很多步骤,但其中大部分都是重复性任务。WebRTC 交换 offer 与网络参数之后,就会尝试直接使用对方的 IP 地址与端口进行直连,这个过程会根据双方网络情况,使用的不同的方式建立连接,后文 NAT(Network Address Translation,即网络地址转换)打洞就是介绍这部分内容。
3.什么是信令服务器?什么是STUN?什么是TURN?
A 与 B 在建立 WebRTC 连接过程中,需要互相知道对方的 IP 与通信端口。那么 A 与 B 要如何知道对方的 IP 与端口呢?答案就是通过信令服务器。 信令服务器的作用是作为一个中间人帮助双方在尽可能少的暴露隐私的情况下建立连接。WebRTC 并没有提供信令传递机制,你可以使用任何方式如 WebSocket 或者 XMLHttpRequest 等,来交换彼此的令牌信息。
STUN 和 TURN 服务器是两种类型的 WebRTC 信令服务器,可用于在构建实时通信应用程序时创建对等 (P2P) 连接。
3.1 什么是STUN?
STUN(NAT 会话遍历实用程序)使用 UDP 协议通过 NAT 来实现 ICE能力。 STUN 允许应用程序发现它们之间和公共互联网上的 NAT 、防火墙的存在和类型。 任何设备都可以使用它来确定 NAT 分配给它的 IP 地址和端口。

通常,STUN 客户端可以向 STUN 服务器发送消息以获取公共 IP 和端口信息,然后基于此公共 IP 和端口信息即可在客户端之间通过互联网进行点对点通信。
3.2 什么是TURN?
TURN(Traversal Using Relays around NAT)是一种协议,可协助 webRTC 应用程序穿越网络地址转换器 (NAT) 或防火墙。 TURN Server 允许客户端通过中间服务器发送和接收数据, TURN 协议是 STUN 的扩展。

在少数情况下,客户端通信端点在不同类型的 NAT 后面,或者当使用对称 NAT 时,通过中继服务器及其称为 TURN 服务器发送媒体可能更容易。
4.WebRTC API 调用
4.1 RTCPeerConnection
RTCPeerConnection 用于点对点之间建立连接以传输音视频数据流,这是 RTCPeerConnection 的任务,为此需要借助一个信令服务器(signaling server)来进行,信令包括 3 种类型的信息:
- Session control messages: 初始化和关闭通信,及报告错误;
- Network configuration: 双方的 IP 地址和端口号(局域网内部 IP 地址需转换为外部的 IP 地址);
- Media capabilities: 双方的浏览器支持使用何种编码以及多高的视频分辨率。
var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;navigator.getUserMedia = navigator.getUserMedia ? 'getUserMedia' : navigator.mozGetUserMedia ? 'mozGetUserMedia' : navigator.webkitGetUserMedia ? 'webkitGetUserMedia' : 'getUserMedia';var v = document.createElement('video');// 创建信令(createOffer)var pc = new PeerConnection();pc.addStream(video);pc.createOffer(function (desc) { pc.setLocalDescription(desc, function () { // send the offer to a server that can negotiate with a remote client });});// 创建回复(createAnswer)var pc = new PeerConnection();pc.setRemoteDescription(new RTCSessionDescription(offer), function () { pc.createAnswer(function (answer) { pc.setLocalDescription(answer, function () { // send the answer to the remote connection }); });});
4.2 RTCDataChannel
RTCDataChannel 接口代表在两者之间建立了一个双向数据通道的连接,可以用
RTCPeerConnection.createDataChannel() 或者在现有的 RTCPeerConnection 上用 RTCDataChannelEvent 类型的 datachannel 事件接收,创建出 RTCDataChannel 类型的对象。
var pc = new RTCPeerConnection();// 获取 RTCPeerConnection 对象var DC = pc.createDataChannel('my channel');// 创建 DataChannel 对象dc.onmessage = function (event) { console.log('received: ' + event.data);};dc.onopen = function () { console.log('datachannel open');};dc.onclose = function () { console.log('datachannel close');};
4.3 访问用户摄像头及麦克风 getUserMedia
WebRTC 支持直接传输音频流和视频流(https://appr.tc/):
const pc = new RTCPeerConnection() ;// 获取RTCPeerConnectionnavigator.getUserMedia({ video: true }, stream => { // 添加视频流到会话中 stream.getTracks().forEach(track => pc.addTrack(track, stream)) // 在网页中预览自己摄像头拍摄到的内容,其中$localVideo表示一个Video对象 $localVideo.srcObject = stream; })
navigator.getUserMedia()还可以和web Audio API相结合,用来处理音频效果:
var range = document.querySelector('input');window.AudioContext = window.AudioContext || window.webkitAudioContext;var audioCtx = new AudioContext();navigator.getUserMedia({ audio: true}, function(stream) { // 创建音频流 var source = audioCtx.createMediaStreamSource(stream); // 双二阶滤波器 var biquadFilter = audioCtx.createBiquadFilter(); biquadFilter.type = 'lowshelf'; biquadFilter.frequenc.value = 1000; biquadFilter.gain.value = range.value; source.connect(biquadFilter); biquadFilter.connect(audioCtx.destination);}, function(error) { console.log(error);});
其实,WebRTC并不只是用来做视频、音频,它还可以用来传输任意数据,包括文件,文本等。上面代码示例可以看到,WebRTC规定了dataChannel这个双工数据通道,而https://snapdrop.net/这个网站就是通过WebRTC进行文件分享。
const pc = new RTCPeerConnection() const dataChannel = pc.createDataChannel('chat') // 监听datachannel事件pc.addEventListener('datachannel', event => { // 接收通信方发送过来的数据 event.channel.addEventListener('message', event => { console.log('message', event.message) }) }) dataChannel.addEventListener('open', () => { // 发送数据,可发送任意数据 dataChannel.send('Hi!') }) dataChannel.addEventListener('close', event => { })
4.4 candidate 事件
当 RTCPeerConnection 实例执行 setLocalDescription()后,RTCPeerConnection 就会探测自己的网络环境,然后用 candidate 事件返回候选网络环境数据,网络环境数据中最重要的是 IP 地址与端口组成的候选通信地址。 candidate 事件中的 event.candidate 主要包含以下几个部分:
- 本机 IP 地址
- 本机用于 WebRTC 通信的端口号
- 候选者类型,包括 host、srflx 和 relay
- 优先级
- 传输协议
具体数据结构的示例如下:
{ address: '192.168.31.67', candidate: 'candidate:2147606101 1 udp 2122260223 192.168.31.67 57959 typ host generation 0 ufrag EaWw network-id 1 network-cost 10', component: 'rtp', foundation: '2147606101', port: 57959, priority: 2122260223, protocol: 'udp', relatedAddress: null, relatedPort: null, sdpMLineIndex: 0, sdpMid: '0', tcpType: null, type: 'host', usernameFragment: 'EaWw',};
candidate 事件 type 字段取值分别为 host、srflx、relay:
- host(Host candidate):从本地网卡上获取的地址
- srflx(Server reflexive candidate):STUN(Session Traversal Utilities for NAT,即 NAT 会话穿越应用程序) 返回的该客户端的地址
- relay(Relay reflexive candidate)::TURN (Traversal Using Relay NAT,即通过 Relay 方式穿越 NAT)服务器为该客户端分配的中继地址
本地的 candidate 与远端 candidate 构成的每一对都有一定的优先级,按优先级排序进行连通性检查。最后从有效的 candidate 组合中选择优先级最高的作为传输地址,用于建立 P2P 连接。
5.网络地址转换(NAT)
网络地址转换(英语:Network Address Translation,缩写:NAT;又称网络掩蔽、IP 掩蔽)在计算机网络中是一种在 IP 数据包通过路由器或防火墙时重写来源 IP 地址或目的 IP 地址的技术。这种技术被普遍使用在有多台主机但只通过一个公有 IP 地址访问互联网的私有网络中。 要建立一个连接需要知道对方的 IP 地址和端口号,在局域网里面一台路由器(基站)可能会连接着很多台设备,例如家庭路由器接入宽带的时候,宽带服务商会分配一个公网的 IP 地址,所有连到这个路由器的设备都共用这个公网 IP 地址。如果两台设备都用了同一个公网 IP:port 去发送请求,服务器返回数据在经过路由器时它就不知道应该转发给哪一个设备。因此路由器需要重写 IP 地址/端口号进行区分,如下图所示:

NAT 设备通常会自动设置各个设备的映射关系,也可以在路由器端去手动设置。如上图的 NAT 维护的映射关系还会和要访问的目标 IP 地址进行绑定,例如同一终端使用同一端口访问不同的目标 IP,就会建立不同的映射关系。

如上示例 NAT 上建立的映射关系如下:
内网 IP 端口 | 外网 IP 端口 | NAT 对外 IP 与端口 |
192.168.1.2:8080 | 39.182.39.30:443 | 10.188.20.10:8000 |
192.168.1.2:8080 | 39.182.39.40:443 | 10.188.20.10:8001 |
所以实际存储的映射关系会包含上面 3 部分内容,这样做的目的是保证网络安全。想象如下例子,终端 192.168.1.2:8080 通过路由器使用 10.188.20.10:8000 访问服务器 A,建立 NAT 映射如果为 192.168.1.2:8080-->10.188.20.10:8000,那么如果有人向 10.188.20.10:8000 发送数据就会转发到 192.168.1.2:8080,这样就会导致内网的服务被外部随意访问,所以 NAT 映射会记录目标地址。当然,由于 NAT 有多种类型,NAT 映射也会存不同,更多内容可参考维基百科或者 WebRTC 网络基础 九、第二节 NAT 打洞原理,下表进行一个简单的归纳。

5.1 NAT 打洞
由于 NAT 有上面 4 种类型,所以两个设备要建立 P2P 连接就要使用不同的方式。
5.1.1 完全锥型 NAT
完全锥型是非常简单的 ,左边是内网的主机,它有自己的内网 IP 地址和端口 ,通过防火墙之后,它形成一个外网的 IP 地址,那么外网的主机要想与内网的主机进行通信的时候,首先要由内网的主机向外发送一个请求,请求外网中的其中一台主机,这样会形成的结果就是它会在 NAT 服务上打一个洞,这样会形成一个外网的 IP 地址和端口。 形成了外网的 IP 地址和端口之后,其他的主机只要获得了这个 IP 地址和端口它都可以向它发送数据。并且可以顺利的通过防火墙发送给内网的主机。这样就可以进行通讯了,这是完全锥型,也是最好穿越的一种 NAT 类型。但是安全性就差很多。

NAT内网计算机IP地址为10.0.0.1,其在端口 8000 上收发消息,映射到 NAT 上的外部IP和端口为 202.123.211.25:8000。 这样Internet 上的任何人都可以向 NAT 上的内网 IP: 端口发送数据包,这些数据包将被传递到 10.0.0.1:8000 的客户端机器。
5.1.2 地址限制锥型 NAT
它的安全性好一些,它会在防火墙上形成一个五元组,即内网主机的 IP 地址和端口、映射后的公网 IP 地址和端口、请求的主机 IP 地址。他们首先有一个公共的步骤,第一步就是要先由内网的主机向外网发送一个请求,在这个防火墙上或者 NAT 服务上形成一个映射表,那形成之后外网的主机就可以和内网的主机进行通讯了。

客户端向服务器 1(IP - 192.248.22.100)发送数据包,然后NAT将客户端的10.0.0.1:8000映射到外网的202.123.211.25:8000, 现在 server1 可以将数据包发送到目的地。 但是,NAT 将阻止来自服务器 2(IP - 192.248.22.200)的数据包,直到客户端向服务器 2 的 IP 地址发送数据包。 一旦客户端将数据包发送到服务器 2,服务器 1 和服务器 2 都可以将数据包发送回客户端。
5.1.3 端口限制锥型 NAT
端口限制型就更加严格一些了,不光是对 IP 地址,还要对端口做限制,所以在防火墙上就形成了六元组,不光有内网的 IP 地址和端口、映射后的公网的 IP 地址和端口、还有你请求的主机的 IP 地址和端口。

如果客户端将数据包发送到 192.248.22.100:10100(包含IP 和端口),NAT 将只允许来自 192.248.22.131:10100 的数据包(到客户端),它丢弃来自 192.248.22.131:10200(相同 ip 但不同端口)的数据包。
5.1.4 对称型 NAT
对称限制型就更加严格了,以前的类型是在防火墙上形成映射后的公网的 IP 地址是保持不变的,大家要找还是能找到它的,虽然不通,但是对于 这个对称型它就不一样了,它就发生了变化,不光是形成了一个 IP 地址和端口,而且还会形成多个,对于每一台主机都会形成一个不同的 IP 地址和端口对。

如果客户端从 10.0.0.1:8000 发送数据到 server1,其外网地址被映射为 202.123.211.25:12345,而如果客户端从同一个端口(10.0.0.1:8000)发送到不同的 IP,则映射的外网地址也会不同(即外网地址被映射为 202.123.211.25:45678)。 server1 只能响应自己的映射,server2也 只能响应自己的映射,两者无法公用。 如果任何一个地址尝试发送到另一个映射的 IP:port,这些数据包将被丢弃。
5.2 WebRTC 打洞这么复杂么?
WebRTC 本身就已经实现 NAT 打洞功能,只需要连接的双方交换了网络端口和 IP 之后,WebRTC 就会自动进行打洞。WebRTC 使用一个叫做交互式连接设施(ICE,Interactive Connectivity Establishment,即交互式连接建立)协议框架,ICE 整合了 STUN 与 TURN。STUN 是用来探测终端 NAT 类型、IP 和端口的服务,WebRTC 获取到 NAT 类型、IP 和端口后就会触发 candidate 事件,然后连接双方交换 IP 与端口,开始打洞。如果打洞失败,那么就会使用 TURN 服务器转发流量。

由于 WebRTC 提供了 ICE,所以使用非常简单,只需在 new RTCPeerConnection 时传入 iceServers 参数即可。googel 提供了免费的 STUN 服务器去帮助打洞,也可以自己架设服务器。
const pc = new RTCPeerConnection({ // 可以传入多个 stun 服务器或者 turn 服务器 iceServers: [ { url: 'stun:stun.l.google.com:19302' }, { url: 'stun:stun1.l.google.com:19302' }, { url: 'stun:stun2.l.google.com:19302' }, { url: 'stun:stun3.l.google.com:19302' }, { url: 'stun:stun4.l.google.com:19302' }, ],});
6.WebRTC vs WebSocket 区别
- 用途区别
- WebSocket允许浏览器和Web服务器之间进行全双工通信.
- WebRTC允许两个浏览器之间的全双工通信。
2. 协议区别
- WebSocket使用TCP协议
- WebRTC使用UDP协议
3. 流量路径
- WebSocket浏览需要经过服务器
- WebRTC是直接连接,浏览不会经过第三方服务器,是一个去中心化的架构模型,简单说就是省带宽。
4. 实时性
- WebSocket延迟高(不是直接连接)
- WebRTC延迟低
通常WebRTC会与WebSocket配合使用,WebSocket的作用主要是用来交换客户端的SDP与网络信息,Websocket传输的内容与真正通信数据无关,只是协助WebRTC建立连接。
参考资料
https://medium.com/orion-innovation-turkey/webrtc-in-a-nutshell-ep-ii-ca8cb33f8ff3
https://datatracker.ietf.org/doc/html/rfc4566
https://medium.com/rahasak/network-address-translation-nat-df84dc1cb06c
https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel
https://caniuse.com/?search=WebRTC
https://www.jianshu.com/p/1022f559a805
https://zhuanlan.zhihu.com/p/421503695
https://github.com/nashaofu/webrtc-demo
https://blog.csdn.net/xyphf/article/details/107297616
https://medium.com/av-transcode/what-is-webrtc-and-how-to-setup-stun-turn-server-for-webrtc-communication-63314728b9d0
https://webrtc.ventures/2020/12/webrtc-signaling-stun-vs-turn/
发表评论