浏览器本身不支持相互之间直接建立信道进行通信,都是通过服务器进行中转。比如现在有两个客户端,甲和乙,他们俩想要通信,首先需要甲和服务器、乙和服务器之间建立信道。甲给乙发送消息时,甲先将消息发送到服务器上,服务器对甲的消息进行中转,发送到乙处,反过来也是一样。这样甲与乙之间的一次消息要通过两段信道,通信的效率同时受制于这两段信道的带宽。同时这样的信道并不适合数据流的传输,如何建立浏览器之间的点对点传输,一直困扰着开发者。
WebRTC 是一个开源项目,旨在使得浏览器能为实时通信(RTC)提供简单的 JavaScript 接口。WebRTC 不仅可传输视频,也可以传输其他数据例如文本、图片等。需要注意的是,WebRTC 并不是浏览器的一个子集,浏览器只是根据 WebRTC 的标准协议实现了 WebRTC 的原生接口。Android 和 IOS 系统也支持 WebRTC 。
WebRTC 应用包括下面四个主要的概念,不打勾表示可以选择不使用
- [x] 信令服务器(Signalling servers)
- [x] ICE 服务器(ICE servers)
- [ ] 媒体服务器 (Media servers)
- [x] JavaScript 接口 (JavaScript API)
信令服务器
信令服务器主要用于在两个用户之间交换信息。虽然 WebRTC 是点对点通信,但还是需要服务器来初始化连接,并传递一些信息。
WebRTC 没有定义用于建立信道的信令的协议,因此可以使用任意的传输方式,例如 WebSocket, XMPP, SIP, AJAX。
你可以使用实时的传输协议比如 WebSocket 来交换数据,也可以使用简单的 GET/POST 方式轮询服务器来获取数据。
信令服务器传送的数据有:
- 协商媒体功能和设置
- 标识和验证会话参与者的身份
- 控制媒体会话、指示进度、更改会话和终止会话
其中只有第一项的必备功能。其他都可以根据业务需求自由调整。
媒体协商最重要的功能在于,为参与点对点通信的两个浏览器之间交换会话描述协议(SDP)。SDP 包含浏览器的 RTP 媒体栈配置所需的全部信息,包括媒体类型(音频、视频、数据)、所需的编解码器,用于编解码器的哥哥参数或设置,以及有关带宽的信息。此外,信令通道还用于交换候选地址,以便进行 ICE 打洞。
ICE 服务器
实现点对点通信的关键在于两个浏览器之间能直接发送和接收数据包,但一般情况下,浏览器或手机都是通过路由器访问 Internet,所以存在网络地址转换(NAT)。位于 NAT 之内的 IP 地址是私有地址,外部无法访问。分配给 NAT 的 IP 地址才是公共地址。NAT 每次从内部到外部转发数据包时都使用公共地址。
交互式建立连接(ICE)是一种标准穿透协议,它利用 STUN 和 TURN 服务器来建立连接。
STUN 服务器可以遍历 NAT,获取浏览器的候选地址,包括私有地址、外层 NAT 的公共 IP 地址等。通信信令通道可以交换候选地址,浏览器一旦发送并收到了候选项,就会开始进行连接检查,若检查成功,便使用该候选项发送媒体。
在大多情况下,通过穿透可以建立直接对等连接。但是,若 NAT 或防火墙限制非常严格,无法建立连接,就只能通过 TURN 服务器中继媒体。
媒体服务器
媒体服务器不是必须的,但在多方会话或需要对媒体做额外处理的情况下可以考虑加入。对于有多个浏览器参与的会议,可以采用一个集中式媒体服务器。在这种情况下,美国浏览器都只需与媒体服务器建立单个连接即可,这种结构的优势是额能够扩展非常大的会话,同时可以在最大限度上减少当有新加入者加入会话事美国浏览器所需的处理工作量。同时,媒体服务器也可对媒体进行分析、处理、保存等工作。
JavaScript 接口
getUserMedia
通过调用 navigator.getUserMedia() 可以获取视频或音频的数据,constraints 参数可以选择是否获取视频音频。下面是一个简单的示例
var constraints = {
audio: false,
video: true
};
var video = document.querySelector('video');
function successCallback(stream) {
if (window.URL) {
video.src = window.URL.createObjectURL(stream);
} else {
video.src = stream;
}
}
function errorCallback(error) {
console.log('navigator.getUserMedia error: ', error);
}
navigator.getUserMedia(constraints, successCallback, errorCallback);
RTCPeerConnection
RTCPeerConnection 是 WebRTC 中最重要的一个接口,用于确定 ICE 服务器、交换 SDP。连接过程如下:
- 创建 RTCPeerConnection 对象
RTCPeerConnection 的参数用于确定 ICE 服务器,下面是使用了 google 开放的 STUN 服务器
let iceServer = {
"iceServers": [{
"url": "stun:stun.l.google.com:19302"
}]
};
let pc = new RTCPeerConnection(servers);
- 将媒体流放入 RTCPeerConnection 对象中
pc.addStream(localStream);
通过 offer 和 answer 交换 SDP 描述符
- 甲和乙各自建立一个PC实例
- 甲通过 PC 所提供的 createOffer() 方法建立一个包含甲的 SDP 描述符的 offer 信令
- 甲通过 PC 所提供的 setLocalDescription() 方法,将甲的SDP描述符交给甲的 PC 实例
- 甲将 offer 信令通过服务器发送给乙
- 乙将甲的 offer 信令中所包含的的SDP描述符提取出来,通过PC所提供的 setRemoteDescription() 方法交给乙的PC实例
- 乙通过PC所提供的 createAnswer() 方法建立一个包含乙的 SDP 描述符 answer 信令
- 乙通过PC所提供的 setLocalDescription() 方法,将乙的 SDP 描述符交给乙的PC实例
- 乙将answer信令通过服务器发送给甲
- 甲接收到乙的answer信令后,将其中乙的SDP描述符提取出来,调用setRemoteDescripttion()方法交给甲自己的PC实例
ICE 打洞
当网络候选可用时,通过信令服务器将其发送到对方浏览器
pc.onicecandidate = function(event) {
if (event.candidate) {
sendToServer(event.candidate)
}
};
当接受到对方网络候选时,将其加入
let candidate = new RTCIceCandidate(candidate);
pc.addIceCandidate(candidate);
- 监听对方发送的媒体是否可用,并播放媒体
pc.onaddstream = event => {
remoteVideo.src = window.URL.createObjectURL(event.stream);
}
RTCDataChannel
RTCDataChannel 是 RTCPeerConnection API 的一部分,只有在创建了 RTCPeerConnection 实例后才能创建数据通道。数据通道可以用于发送文本或是文件。
pc = new RTCPeerConnection();
dc = pc.createDataChannel('dc');
dc.onmessage = event => console.log(event.data);
dc.send('text');
dc.sed(new arraybuffer(32))
在另一端可以使用 ondatachannel 获得 RTCDataChannel 对象
pc.ondatachannel = event => dc = event.channel;
参考资料
https://codelabs.developers.google.com/codelabs/webrtc-web