WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。
在认识Web RTC之前,需要先链接几个重要的框架:
ICE(Interactive Connectivity Establishment)
ICE诞生的原因:由于Peer对Peer直连的方式受多重因素的限制,比如防火墙,公共IP,路由等。因此需要一个灵活的框架去除这些因素的影响,允许web浏览器与Peer节点连接的框架。而这个框架统称为ICE,ICE的背后使用的是STUN和TURN服务。
STUN(Session Traversal Utilities for NAT)
STUN主要解决两个问题:查询自己的公共IP和定位阻止Peer创建连接失败的原因。
PeerA要和PeerB通信,流程大致如下:
PeerA -> STUN : 我的公网IP是啥?
STUN -> PeerA: 你的公网IP是 208.121.55.130:3255
PeerB -> STUN : 我的公网IP是啥?
STUN -> PeerB: 你的公网IP是 208.131.59.131:3253
通过询问STUN后Peers就知道自己公网通信地址,就可以顺利的和其他Peer创建连接了。
NAT (Network Address Translation)
NAT的出现,本质上是为了解决IPV4地址不够用的问题,地球上的设备如此之多,设备之间要通信必须是公共IP, 但是公共的IP地址有限,因此NAT将通过 私有IP段+端口 的方式唯一定位一台设备,这样就顺利的解决私有网段上设备通信的问题。
但是,仅解决这些问题还不够!因为有些路由器对NAT的访问再做一层Symmetric NAT限制,同样会导致创建连接失败,因此为了解决这个问题,又引入了TURN.
TRUN(Traversal Using Relays around NAT)
使用TURN遍历意味着通过打开一个与TURN服务器的连接并通过该服务器转发所有信息来绕过Symmetric NAT限制。PeerA创建一个与TURN服务器的连接,并告诉其他的Peer发送数据包到服务器,然后TURN再将转发给请PeerA。这显然会带来一些开销,所以只有在万不得已的情况下才使用它。
SDP(Session Description Protocol)
SDP是一种描述连接的多媒体内容的标准,如分辨率、格式、编解码器、加密等,以便数据传输时双方能够相互理解。本质上,这是描述内容的元数据,而不是媒体内容本身。因此,从技术上讲,SDP不是真正的协议,而是用于描述设备之间共享媒体的连接的数据格式。
Signaling server
在webrtc中,信令服务器起着重要的作用,信号服务器的工作是充当中介,让两个对等点查找和建立连接,同时尽可能减少潜在的私有信息的暴露。webrtc可以使用WebSocket或者XMLHttpRequest进行peers之间的通信。
1. 建立了交换消息的机制
chat server在client和server使用WebSocket API发送json格式的数据,通过指定不同的消息类型来处理不同的任务,比如注册新用户,设置用户名,发送广播消息等。在源码的chatserver.js中对此操作进行了封装。
1.找到接受方,并调用sent方法发送消息,其中connectionArray的元素是WebSicjet对象。
function sendToOneUser(target, msgString) {
var isUnique = true;
var i;
for (i=0; i<connectionArray.length; i++) {
if (connectionArray[i].username === target) {
connectionArray[i].send(msgString);
break;
}
}
}
- 如果没有指定接收方,则对所有人进行消息的发送。
if (sendToClients) {
var msgString = JSON.stringify(msg);
var i;
if (msg.target && msg.target !== undefined && msg.target.length !== 0) {
sendToOneUser(msg.target, msgString);
} else {
for (i=0; i<connectionArray.length; i++) {
connectionArray[i].send(msgString);
}
}
}
2. 定义消息的格式
也就是定义发送端和接收端如何发送和处理接受消息。消息的格式如下:
type: video-offer | video-answer
name: 发送方的名字
target: 接收方的名字
sdp: 发送方的sdp
首先,caller和callee之间需要互换session descriptions, caller会发送type='video-offer'的消息给callee,callee会发送type='video-answer'的格式给caller。在这个过程中,双方协商了之后使用寿命codec进行media的通信,但是并未开始media的传输,media的传输是在ICE上完成的。
3. 互换ICE candidates
Peer之间需要互相发送ICE candidates来协商确定自己是否在对方的candidates列表里,即使meida流已经在传输,他们直接依然可能还在互换candidates信息。
icecandidate事件被发送到RTCPeerConnection,然后使用pc.setLocalDescription(offer)完成添加SDP。
一旦两个Peers之间发现互相匹配了之后,他两就会通过SDP来建立连接,然后开始media的传输,如果有新的更高质量的candidates请求进来,那么已经开始传输的media也会根据需要改变格式。
互换ICE candidates是消息格式为:
type: new-ice-candidate
target: 接收方的名字
sdp: candidate的sdp
4. 消息传输流程
信令传输的过程涉及到信令服务器在两个对等点之间交换消息。当然,确切的过程会有所不同,但一般来说,处理信号信息的几个关键点是:
- 每个用户的web浏览器
- 信令服务器
- 托管聊天服务的web服务器
加入Naomi和Priya需要使用视频会议,那么其事件的流程如下:
当每个Peer的ICE层开始发送candidates时,它将进入链中各个点之间的交换,如下所示:
下面将通过一个小的demo展示WebRTC是如何通过WebSocket实现聊天的。
demo的效果请访问:https://webrtc-from-chat.glitch.me/
demo的源码: https://github.com/mdn/samples-server/tree/master/s/webrtc-from-chat
会话描述(Session decription)
WebRTC连接上端点的配置称为会话描述。该描述包括有关正在发送的媒体类型,其格式,使用的传输协议,端点的IP地址和端口的信息,以及描述媒体传输端点所需的其他信息。
当用户向另一个用户发起WebRTC呼叫时,会创建一个特殊的描述,称为offer。此描述包括有关呼叫者为呼叫建议的配置的所有信息。然后,接收者用一个answer来回应。以此方式,两个设备彼此共享为了交换媒体数据所需的信息。这种交换是使用ICE处理的,该协议允许两个设备使用中介程序交换要约和答复,即使两个设备之间都被网络地址转换(NAT)隔开。
然后,每个Peer都保留两个描述:本地描述(描述自己)和远程描述(描述呼叫的另一端)。
offer/answer过程不仅在首次建立呼叫时执行,而且在呼叫格式或其他配置需要更改时都执行。不管是新呼叫还是重新配置现有呼叫,以下这些offer/answer都是必须执行的基本步骤(ICE逻辑除外):
- 呼叫者通过捕获本地媒体
navigator.mediaDevices.getUserMedia()
- 呼叫者创建
RTCPeerConnection
并调用RTCPeerConnection.addTrack()
- 呼叫者调用用
RTCPeerConnection.createOffer()
以创建offer。 - 呼叫者调用
RTCPeerConnection.setLocalDescription()
以将该offer设置为本地描述(即连接本地端的描述)。 - 在
setLocalDescription()
之后,呼叫方要求STUN服务器生成ICE candidates - 呼叫者使用信令服务器将offer发送到呼叫的预期接收者。
- 接收者收到offer,并调用
RTCPeerConnection.setRemoteDescription()
将其记录为远程描述(连接另一端的描述)。 - 接收者会在通话结束时进行所有必要的设置:捕获其本地媒体流,然后通过
RTCPeerConnection.addTrack()
将媒体流对接到连接上。 - 然后,接收者通过调用
RTCPeerConnection.createAnswer()
来创建answer。 - 接收者调用
RTCPeerConnection.setLocalDescription()
,并传入创建answer,以将answer设置为其本地描述。至此,接受者已经知晓connection两端的配置。 - 接收者使用信令服务器将answer发送给呼叫者。
- 呼叫者收到answer。
- 呼叫者调用
RTCPeerConnection.setRemoteDescription()
以将answer案设置为呼叫结束的远程描述。现在,呼叫者也同步了两端的配置化信息。流媒体就可以开始按配置传输了。