摘要: 深入JS系列18。
- 原文:JavaScript 是如何工作的:WebRTC 和对等网络的机制!
- 作者:前端小智
Fundebug经授权转载,版权归原作者所有。
这是专门探索 JavaScript 及其所构建的组件的系列文章的第 18 篇。
如果你错过了前面的章节,可以在这里找到它们:
- JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述!
- JavaScript 是如何工作的:深入V8引擎&编写优化代码的5个技巧!
- JavaScript 是如何工作的:内存管理+如何处理4个常见的内存泄漏!
- JavaScript 是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!
- JavaScript 是如何工作的:深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!
- JavaScript 是如何工作的:与 WebAssembly比较 及其使用场景!
- JavaScript 是如何工作的:Web Workers的构建块+ 5个使用他们的场景!
- JavaScript 是如何工作的:Service Worker 的生命周期及使用场景!
- JavaScript 是如何工作的:Web 推送通知的机制!
- JavaScript是如何工作的:使用 MutationObserver 跟踪 DOM 的变化!
- JavaScript是如何工作的:渲染引擎和优化其性能的技巧!
- JavaScript是如何工作的:深入网络层 + 如何优化性能和安全!
- JavaScript是如何工作的:CSS 和 JS 动画底层原理及如何优化它们的性能!
- JavaScript的如何工作的:解析、抽象语法树(AST)+ 提升编译速度5个技巧!
- JavaScript是如何工作的:深入类和继承内部原理+Babel和 TypeScript 之间转换!
- JavaScript是如何工作的:存储引擎+如何选择合适的存储API!
- JavaScript是如何工作的: Shadow DOM 的内部结构+如何编写独立的组件!
概述
WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。
在此之前,P2P技术(如桌面聊天应用程序)可以做一些网络做不到的事情,WebRTC 填补了 Web 这一关键空白点。
WebRTC 是一项实时通信技术,它允许浏览器或者 app 之间可以不借助中间媒介的情况下,建立浏览器之间点对点的连接,实现视频流和音频流或者其他任意数据的传输。本文中讨论这一点,还支讨论以下主题,以便让你全面了解 WebRTC 的内部结构:
- 点对点通信 (Peer-To-Peer communication)
- 防火墙和NAT穿透 (Firewalls and NAT Traversal)
- 信令、会话和协议 (Signaling, Sessions, and Protocols)
- WebRTC APIs
点对点通信
为了通过 Web 浏览器与另一个对等点进行通信,每个 Web 浏览器必须经过以下步骤:
- 是否同意进行通信
- 彼此知道对方的地址
- 绕过安全和防火墙保护
- 实时传输所有多媒体通信
基于浏览器的点对点通信相关的最大挑战之一是知道如何定位和建立与另一个 Web 浏览器的网络套接字连接,以便双向传输数据。
当 Web 应用程序需要一些数据或资源时,它从某个服务器获取数据或资源,仅此而已。但是,如果想创建点对点视频聊天,通过直接连接到其他人的浏览器——你不知道对方地址,因为另一个浏览器不是已知的 Web服务器。因此,为了建立点对点连接,还需要做更多的工作。
防火墙和 NAT 穿透 (Firewalls and NAT Traversal)
NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地 IP 地址 (即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用 NAT 方法。
NAT(Network Address Translation,网络地址转换)简单来说就是为了解决 IPV4 下的IP地址匮乏而出现的一种技术。
举例,就是通常我们处在一个路由器之下,而路由器分配给我们的地址通常为191.168.0.21 、191.168.0.22如果有n个设备,可能分配到192.168.0.n,而这个IP地址显然只是一个内网的IP地址,这样一个路由器的公网地址对应了 n 个内网的地址,通过这种使用少量的公有 IP 地址代表较多的私有 IP 地址的方式,将有助于减缓可用的IP地址空间的枯竭。
NAT技术会保护内网地址的安全性,所以这就会引发个问题,就是当我采用P2P之中连接方式的时候,NAT会阻止外网地址的访问,这时我们就得采用 NAT 穿透了。
这就是 NAT (STUN) 的会话遍历实用程序和围绕 NAT (TURN)服务器使用中继进行遍历的原因。为了让WebRTC 技术能够正常工作,首先会向 STUN 服务器请求你的公开IP地址。可以把它想象成你的计算机向远程服务器进行查询,该服务器询问它接收查询的IP地址,然后远程服务器用它看到的 IP 地址进行响应。
假设这个过程有效,并且你接收到你面向公众的 IP 地址和端口,那么你就能够告诉其他对等方如何直接连接到你。这些对等点还可以使用 STUN 或 TURN 服务器做同样的事情,并可以告诉你用什么地址与它们联系。
STUN(Simple Traversal of UDP over NATs,NAT 的UDP简单穿越)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT 路由器之后的主机之间建立UDP通信。该协议由RFC 3489定义。目前RFC 3489协议已被RFC 5389协议所取代,新的协议中,将STUN定义为一个协助穿越NAT的工具,并不独立提供穿越的解决方案。它还有升级版本RFC 7350,目前正在完善中。
TURN的全称为Traversal Using Relay NAT,即通过Relay方式穿透 NAT,TURN 应用模型通过分配TURNServer的地址和端口作为客户端对外的接受地址和端口,即私网用户发出的报文都要经过TURNServer进行Relay转发,这种方式应用模型除了具有STUN方式的优点外,还解决了STUN应用无法穿透对称NAT(SymmetricNAT)以及类似的Firewall设备的缺陷
信令、会话和协议
上述网络信息发现过程是较大的信令主题的一部分,其基于 WebRTC 情况下的 JavaScript 会话建立协议(JSEP)标准。 信令涉及网络发现和 NAT 穿透,会话创建和管理,通信安全性,媒体能力元数据和协调以及错误处理。
为了使连接起作用,对等方必须获取元数据的本地媒体条件(例如,分辨率和编解码器功能),并收集应用程序主机的可能网络地址,用于来回传递这些关键信息的信令机制并未内置到 WebRTC API 中。
信令不是由 WebRTC 标准指定的,也不是由其 Api 实现的,这样可以保持技术和协议的灵活性。信令和处理它的服务器由 WebRTC 应用程序开发人员处理。
假设 WebRTC 浏览器的应用程序能够使用 STUN 确定其面向公共的IP地址,下一步是实际地与对等方协商并建立网络会话连接。
初始会话协商和建立使用专门用于多媒体通信的信令/通信协议进行,该协议还负责管理会话的管理和终止规则。
其中一个协议是会话启动协议(称为SIP)。请注意,由于WebRTC信令的灵活性,SIP不是唯一可以使用的信令协议。所选的信令协议还必须与一个称为会话描述协议(SDP)的应用层协议一起工作,该协议在WebRTC的情况下使用。所有特定于多媒体的元数据都使用SDP协议传递。
尝试与另一个对等体通信的任何对等体(即,WebRTC-利用应用程序)生成一组交互式连接建立协议(ICE)候选者。 候选者代表要使用的IP地址,端口和传输协议的给定组合。 请注意,单台计算机可能具有多个网络接口(无线,有线等),因此可以为每个接口分配多个IP地址。
这是一个来自MDN的图表,描述了这种交换。
建立连接
每个对等点首先建立它所描述的面向公共的IP地址。然后动态创建信令数据“通道”来检测对等点,并支持对等协商和会话建立。
外部世界不知道或无法访问这些“通道”,因此需要一个惟一的标识符来访问它们。
请注意,由 于WebRTC 的灵活性,以及该标准没有指定信令流程这一事实,考虑到所使用的技术,“通道”的概念和使用可能略有不同,事实上,有些协议不需要“通道”机制进行通信。
这里假设在本文的实现中使用了“通道”。
一旦两个或更多个对等体连接到相同的“信道”,则对等点能够通信并协商会话信息,此过程有点类似于发布/订阅模式。 基本上,发起对等体使用诸如会话发起协议 SIP 和 SDP 之类的信令协议发送“offer(请求)”,发起者等待从连接到给定“信道”的任何接收器接收“answer(应答)”。
一旦收到答复,就会发生以下过程,确定并协商每个对等点收集的最佳交互连接建立协议(ICE)候选者。 一旦选择了最佳 ICE 候选者,基本上所有所需的元数据,网络路由(IP地址和端口)以及用于为每个对等体通信的媒体信息达成一致。 然后,完全建立并激活对等点之间的网络套接字会话。 接下来,由每个对等体创建本地数据流和数据信道端点,并且最终使用所采用的任何双向通信技术以双向方式传输多媒体数据。
如果商定最佳 ICE 候选方案的过程失败(有时确实由于使用了防火墙和 NAT 技术而发生这种情况),那么可以使用 TURN 服务器作为中继。这个过程基本上使用一个充当中介的服务器,它在对等点之间中继任何传输的数据。请注意,这不是真正的对等通信,在这种通信中,对等点直接双向地向彼此传输数据。
当使用 TURN 回退进行通信时,每个对等方不再需要知道如何相互联系和传输数据。 相反,它们需要知道公共 TURN 服务器在通信会话期间发送和接收实时多媒体数据。
重要的是要明白,这绝对是一个失败的安全措施和最后的手段。TURN 服务器需要非常健壮,具有广泛的带宽和处理能力,并处理潜在的大量数据。因此,使用 TURN 服务器显然会带来额外的成本和复杂性。
SIP(Session Initiation Protocol,会话初始协议)是由IETF(Internet Engineering Task
Force,因特网工程任务组)制定的多媒体通信协议。它是一个基于文本的应用层控制协议,用于创建、修改和释放一个或多个参与者的会话。广泛应用于CS(Circuit
Switched,电路交换)、NGN(Next Generation Network,下一代网络)以及IMS(IP Multimedia Subsystem,IP多媒体子系统)的网络中,可以支持并应用于语音、视频、数据等多媒体业务,同时也可以应用于Presence(呈现)、Instant Message(即时消息)等特色业务。可以说,有IP网络的地方就有SIP协议的存在。
SDP 完全是一种会话描述格式(对应的RFC2327) ― 它不属于传输协议 ― 它只使用不同的适当的传输协议,包括会话通知协议(SAP)、会话初始协议(SIP)、实时流协议(RTSP)、MIME 扩展协议的电子邮件以及超文本传输协议(HTTP)。SDP协议是也是基于文本的协议,这样就能保证协议的可扩展性比较强,这样就使其具有广泛的应用范围。SDP 不支持会话内容或媒体编码的协商,所以在流媒体中只用来描述媒体信息。媒体协商这一块要用RTSP来实现.
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
WebRTC APIs
- MediaStream — MediaStream用来表示一个媒体数据流,允许你访问输入设备,如麦克风和 Web摄像机,该 API 允许从其中任意一个获取媒体流。
- RTCPeerConnection — RTCPeerConnection 对象允许用户在两个浏览器之间直接通讯 ,你可以通过网络将捕获的音频和视频流实时发送到另一个 WebRTC 端点。使用这些 Api,你可以在本地机器和远程对等点之间创建连接。它提供了连接到远程对等点、维护和监视连接以及在不再需要连接时关闭连接的方法。
- RTCDataChannel — 表示一个在两个节点之间的双向的数据通道,每个数据通道都与RTCPeerConnection 相关联。
MediaStream (别名getUserMedia)
MediaStream API 代表媒体流的同步。比如,从摄像头和麦克风获取的媒体流具有同步视频和音频轨道。
MediaDevices.getUserMedia()
会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。
它返回一个 Promise 对象,成功后会 resolve 回调一个 MediaStream 对象。若用户拒绝了使用权限,或者需要的媒体源不可用,promise 会 reject 回调一个 PermissionDeniedError 或者 NotFoundError 。
可以通过 navigator
对象访问 MediaDevice 单例,如下所示:
通常你可以使用 navigator.mediaDevices 来获取 MediaDevices ,例如:
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* 使用这个stream stream */
})
.catch(function(err) {
/* 处理error */
});
请注意,constraints
参数是一个包含了video 和 audio两个成员的MediaStreamConstraints 对象,用于说明请求的媒体类型。必须至少一个类型或者两个同时可以被指定。如果浏览器无法找到指定的媒体类型或者无法满足相对应的参数要求,那么返回的Promise对象就会处于rejected[失败]状态,NotFoundError作为rejected[失败]回调的参数。
从版本25开始,基于 Chromium 的浏览器允许将来自 getUserMedia()
的音频数据传递给音频或视频元素(但请注意,默认情况下,媒体元素将被静音)。
getUserMedia
还可以用作 Web 音频 API 的输入节点:
function gotStream(stream) {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
// Create an AudioNode from the stream
var mediaStreamSource = audioContext.createMediaStreamSource(stream);
// Connect it to destination to hear yourself
// or any other node for processing!
mediaStreamSource.connect(audioContext.destination);
}
navigator.getUserMedia({audio:true}, gotStream);
约束
getUserMedia()
是一个可能涉及重大隐私问题的 API,规范将其用于用户通知和权限管理的非常特定的需求。getUserMedia()
在打开任何媒体收集输入(如网络摄像头或麦克风)之前,必须始终获得用户许可。浏览器可能提供每个域一次的权限特性,但它们必须至少在第一次请求,如果用户选择这样做,则必须特别授予正在进行的权限。
同样重要的是关于通知的规则。浏览器需要显示一个指示器,该指示器显示正在使用的摄像机或麦克风,超出可能存在的任何硬件指示器。它们还必须显示一个指示符,表明已授予使用设备进行输入的权限,即使该设备目前没有进行主动记录
RTCPeerConnection
RTCPeerConnection 它代表了本地端机器与远端机器的一条连接。该接口提供了创建,保持,监控,关闭连接的方法的实现。的作用是在浏览器之间建立数据的“点对点”(peer to peer)通信.
下面是 WebRTC 架构图,展示了 RTCPeerConnection 的作用:
从 JavaScript 的角度来看,从这个图中要理解的主要事情是 RTCPeerConnection
为 Web 开发人员提供了一个抽象,从复杂的内部结构中抽象出来。使用WebRTC的编解码器和协议做了大量的工作,方便了开发者,使实时通信成为可能,甚至在不可靠的网络:
- 丢包隐藏
- 回声抵消
- 带宽自适应
- 动态抖动缓冲
- 自动增益控制
- 噪声抑制与抑制
- 图像清洗
RTCDataChannel
除了视频和音频,webRTC 还可以传输其他数据,RTCDataChannel
API支持对等交换任意数据。
应用场景:
- 游戏
- 远程桌面应用程序
- 实时文本聊天
- Web文件传输
API充分利用了 RTCPeerConnection
强大和灵活的点对点通信
- 利用
RTCPeerConnection
会话。
* 多通道同步通道。
- 可靠和不可靠的传递语义(delivery semantics)。
- 内置安全(DTLS)和阻塞控制。
* 能够使用或不使用音频或视频。
语法类似于已知的 WebSocket,使用 send()
方法和 message
事件:
var peerConnection = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]}
);
peerConnection.ondatachannel = function(event) {
receiveChannel = event.channel;
receiveChannel.onmessage = function(event){
document.querySelector("#receiver").innerHTML = event.data;
};
};
sendChannel = peerConnection.createDataChannel("sendDataChannel", {reliable: false});
document.querySelector("button#send").onclick = function (){
var data = document.querySelector("textarea#send").value;
sendChannel.send(data);
};
通信直接在浏览器之间进行,因此即使需要中继(TURN)服务器,RTCDataChannel 也可以比 WebSocket快得多。
现实世界中的WebRTC
实际应用中,WebRTC 需要服务器,无论多简单,下面四步是必须的:
- 用户通过交换名字之类的信息发现对方。
- WebRTC 客户端应用交换网络信息。
- 客户端交换媒体信息包括视频格式和分辨率。
- WebRTC 客户端穿透 NAT 网关和服务器。
换句话说,WebRTC 需要四种类型的服务器端功能:
- 用户发现和通信
- 信令
- NAT/防火墙穿透
- 中继服务器,防止端到端的通信失败
可以说基于 STUN 和TURN协议的 ICE 框架,使得 RTCPeerConnection 处理 NAT 穿透和其他网络难题成为可能。
ICE 框架用于端到端的连接,比如说两个视频聊天客户端。起初,ICE 尝试通过 UDP 直接连接两端,这样可以保证低延迟。在这个过程中,STUN 服务器有一个简单的任务:使 NAT 后边的端能找到它的公网地址和端口(谷歌有多个STUN服务器,其中一个用在了apprtc.appspot.com例子)。
如果 UDP 传输失败,ICE 会尝试 TCP:首先是 HTTP,然后才会选择 HTTPS。如果直接连接失败,通常因为企业的 NAT 穿透和防火墙,此时 ICE 使用中继(Relay)服务器。换句话说,ICE 首先使用STUN 和 UDP 直接连接两端,失败之后返回中继服务器。‘finding cadidates’
就是寻找网络接口和端口的过程。
安全
实时通信应用或插件会在许多方面忽视了安全性:
- 浏览器之间、浏览器与服务器之间的音视频或其他数据没有加密。
- 应用在用户没有察觉的情况下录制和分发音视频。
- 恶意软件或病毒可能入侵了正常的插件或应用。
WebRTC 的许多特性可以避免这些问题:
- WebRTC 采用类似 DTLS 和 SRTP 的安全协议。
* 所有WebRTC组件都必须进行加密,包括信令机制。
* WebRTC 不是一个插件:它的组件运行在浏览器沙盒中,而不是在一个单独的进程中,组件不需要单独安装,并且在浏览器更新时都会更新。
- 摄像头和麦克风的访问必须经过明确准许,当摄像头和麦克风运行时,界面上会清楚的显示出来。
WebRTC是一种非常有趣和强大的技术,用于在浏览器之间进行某种形式的实时流。
原文:How JavaScript works: WebRTC and the mechanics of peer to peer networking