!WebRTC中文社区原创,转载请注明出处,谢谢,水平有限,部分意思可能不到位,建议参考英文原帖!
WebRTC 能够p2p视频通话
可是...
WebRTC 仍然须要几个服务:
- 信令服务: 使客户端之间交换数据用来协调创建通话
- NAT穿透服务:应付NATs和防火墙
这篇文章会教你怎么搭建信令服务,和用STUN/TURN服务去作nat穿透。另外,咱们会解释WebRTC是怎么作到多端通话的。以及如何和VoIP/PSTN(电话)创建通话。
一.什么是信令服务(Signaling)?
信令是一个协调沟通的过程,为了让一个WebRTC应用发起一个“通话”,客户端间须要交换如下信令信息:
1.发起和关闭一个通话的控制信息;
2.错误信息;
3.媒体元数据,好比编码解码设置,带宽和媒体类型;
4.Key数据,用于确保安全通信;
5.网络数据,好比主机在外网下的IP地址和端口。
客户端的信令处理须要一种来回传递信息的方法,这种机制没有被WebRTC定义,你须要本身去建立它。下面咱们将描绘几种构建信令服务的方法。在此以前,先讲几个概念……
为何WebRTC没有定义信令?
为了不冗余和最大化兼容已经确立的技术,WebRTC没有指定信令的方法和协议。
-------------------------------
(WebRTC设计思想是彻底指定和控制媒体层,可是让signaling层尽可能脱离应用,缘由是不一样的应用可能会使用不一样的协议,好比已经存在的SIP或者Jingle呼叫协议等。这份协议中,须要交换的关键信息是多媒体会议的描述信息,包括在媒体层肯定必要的传输方式和 媒体配置信息)
------------------------------------------------
JSEP的结构一样避免了让浏览器保存状态信息,若是让浏览器成为一个保存信令状态的机器,会出现一个问题,就是每次当页面重载的时候,信令会丢失。因此更好的方案是用服务器保存信令状态。

JSEP协议要求端对端之间须要发起(offer)和回应(answer)上面提到的数据。
offer和answer用SDP(Session Description Protocol format信令描述协议格式)交流,像这样:

若是想知道全部的SDP表明的意思,能够看下这个连接:IEFT examples
记住,WebRTC这样设计是为了让offer端和answer端可以在tweaked以前经过SDP文档设置好参数。
举个例子: apprtc.appspot.com 里的preferAudioCodec()方法用来设置默认的编解码方式和比特率,SDP用JavaScript比较难操做,将来的版本可能会用JSON代替,可是SDP仍是有一些优点的。
二.RTCPeerConnection + 信令: offer,answer和candidate
RTCPeerConnection 是WebRTC客户端在两端创建音视频通信的API。
初始化RTCPeerConnection进程须要两个步骤:
1.肯定当期的媒体条件,例如分辨率,编解码能力。这些是给offer和answer的原始数据。
2.得到应用主机的网络地址(也就是candidate)
一旦这些本地数据被肯定好了,就必须经过信令机制在不一样端交换。
假设A想呼叫B,下面是整个offer/answer机制的细节:
1.A建立一个RTCPeerConnection对象。
2.A用RTCPeerConnection的createOffer()方法建立一个offer(用SDP协议描述)。
3.A用他的offer设置本地描述setLocalDescription()。
4.A序列化offer,而且用信令机制发送给B.
5.B用A的offer调用setRemoteDescription()设置对方的描述,B的RTCPeerConnection就知道了A的配置了。
6.B调用createAnswer(),若是成功会返回一个本地的session描述,既B的answer。
7.B用她的answer设置为本地的描述,经过调用setLocalDescription().设置本地描述
8.B用信令机制发送序列化后的answer给A。
9.A设置B的answer为对方session描述,经过调用setRemoteDescription()设置对方的描述.
(至此,A和B都设置了本地和对方的描述)
A和B还须要交换网络信息。'finding candidates' 指的是用ICE framework.去发现网络接口和端口。
1.A用一个onIceCandidate handler建立一个RTCPeerConnection对象。
2.当网络candidates有效时这个handler会被调用。
3.在这个handler里,A发送序列化的candidates数据给B,经过信令通道。
4.当B从A得到一个candidate信息,她调用addIceCandidate()去给对方描述添加candidate。
JSEP支持ICE Candidate Trickling技术(容许呼叫者在首次初始化offer后,逐次发送candidates给被呼叫者,这是为了让被呼叫者开始设置链接而不用等到所有的candidates到达)
WebRTC 的信令编码
下面是W3C code exampleW3C代码样例,概况了完整的signaling过程。
样例假设已经有了信令机制:SignalingChannel。Signaling 会在下面探讨比较多的细节。php
- var signalingChannel = new SignalingChannel();
- var configuration = {
- 'iceServers': [{
- 'url': 'stun:stun.example.org'
- }]
- };
- var pc;
- // call start() to initiate
- function start() {
- pc = new RTCPeerConnection(configuration);
- // send any ice candidates to the other peer
- pc.onicecandidate = function (evt) {
- if (evt.candidate)
- signalingChannel.send(JSON.stringify({
- 'candidate': evt.candidate
- }));
- };
- // let the 'negotiationneeded' event trigger offer generation
- pc.onnegotiationneeded = function () {
- pc.createOffer(localDescCreated, logError);
- }
- // once remote stream arrives, show it in the remote video element
- pc.onaddstream = function (evt) {
- remoteView.src = URL.createObjectURL(evt.stream);
- };
- // get a local stream, show it in a self-view and add it to be sent
- navigator.getUserMedia({
- 'audio': true,
- 'video': true
- }, function (stream) {
- selfView.src = URL.createObjectURL(stream);
- pc.addStream(stream);
- }, logError);
- }
- function localDescCreated(desc) {
- pc.setLocalDescription(desc, function () {
- signalingChannel.send(JSON.stringify({
- 'sdp': pc.localDescription
- }));
- }, logError);
- }
- signalingChannel.onmessage = function (evt) {
- if (!pc)
- start();
- var message = JSON.parse(evt.data);
- if (message.sdp)
- pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
- // if we received an offer, we need to answer
- if (pc.remoteDescription.type == 'offer')
- pc.createAnswer(localDescCreated, logError);
- }, logError);
- else
- pc.addIceCandidate(new RTCIceCandidate(message.candidate));
- };
- function logError(error) {
- log(error.name + ': ' + error.message);
- }
复制代码
查看“单页面”视频聊天的例子simpl.info/pc.能够在 控制台的lgo看到offer/answer 和candidate 的交换过程。
若是你想了解更多,能够在Chrome浏览器打开 chrome://webrtc-internals 或在opera打开 opera://webrtc-internals下载完整的代码。
三.成员发现机制(Peer discovery)这里有个问题: 我怎么发现谁能够通话?
对于电话,咱们有电话号码和目录。对于在线视频聊天,咱们须要身份和业务管理系统和一种让用户开始会话的手段。
WebRTC apps须要一种 让客户端标示本身以即可以开始和加入会话的方法。
成员发现机制Peer discovery mechanisms没有被WebRTC定义,在这里咱们不用作选择。
这个过程能够像发送一个URL地址这么简单,对于视频聊天应用,好比 talky.io, tawk.com and browsermeeting.com,你经过分享一个通用连接邀请别人进入一个会话。
开发者Chris Ball开发了一个有趣的实验:serverless-webrtc,可让WebRTC呼叫参与者分享元数据,经过任何信息服务,好比IM,email或者信鸽。
四.怎么建立一个signling服务?
再说一遍:信令机制没有被WebRTC标准定义,不管你选择哪一种 ,你须要一个中间服务器去交换信令信息和不一样客户端间的应用数据。
庆幸的是,信令信息很小,大部分交换都是在通话开始的时候。
在测试 apprtc.appspot.com 和 samdutton-nodertc.jit.su 时,咱们发现一个 视频会话,总共有大概30-45的信息被信令服务器处理,信息大小大概是10kB。
除了相对要求不高的带宽,WebRTC 信令服务器不用花费过多的内存和进程,由于只须要转发信息和保持不多的会议状态数据(好比那个客户端被链接了)
小贴士 :
信令机制不只能够用来交换会话元数据,也能用来传达应用数据。它就是个信息服务。
五.从服务端推信息给客户端
一个信令服务器须要是双向的:客户端到服务器和服务器到客户端。
双向通信违反了HTTP 客户端/服务端 请求/回复的模式,可是有一些发展多年的技术,例如long polling(长时间轮询) 被用来从服务端发送数据给一个运行中的web应用。
最近,EventSource API 被普遍的应用,它容许“服务端发送事件”:数据经过HTTP从服务端发送给浏览器。
这里有个简单的demo:simpl.info/es。
EventSource被设计为一种消息传送方式,可是它能够跟XHR 结合作成一个交换signaling的服务:从一个呼叫者传递信息,由XHR 请求传递,推送给被呼叫者。
WebSocket 是一种更天然的解放方案,它是为了全双工 客户端-服务端通信设计的(信息能够在同一时间在两个端传递)。
用纯WebSocket或者Server-Sent Events (EventSource) 作为signaling服务的优势是后端调用这些APIs能够用多种Web框架实现,在使用PHP,Python和Ruby的状况下。
大约有四分之三的浏览器支持WebSocket ,更重要的是,全部支持WebRTC的桌面浏览器和移动浏览器都支持WebSocket。
TLS(安全传输层协议)应该用于全部的连接,已确保信息不会被截断。
同时用proxy traversal减小问题(更多关于WebSocket 和proxy traversal的资料能够看WebRTC chapter 和WebSocket Cheat Sheet)
apprtc.appspot.com 的信令是经过Google App Engine Channel API完成的,Google App Engine Channel API是使用了Comet技术(长时间轮询)让APP后端和web客户端 实现推送通信功能。这里有个代码预演。
另一种方案,能够经过Ajax去轮询服务端获取signaling,但会致使一堆多余的网络请求,特别是在移动客户端。
在一个会话被肯定后,用户仍然须要去轮询signaling信息,由于会话可能会被其余用户改变或者终止。
《WebRTC》这本书就用了这种通过优化轮询频率的方法。
信令压缩
虽然一个信令服务器在每个客户端中花费至关小的带宽和CPU,可是一个广泛使用的应用可能须要从不一样的地点处理不少信息,而且有不少高的并发数。一个大流量的WebRTC 应用须要心理服务端去处理至关大的负荷。
这里咱们不讲细节,下面有一些 处理高数据量,高性能的信息通信设置:
1.XMPP,最初被称为Jabber:一种被开发用来即时通信的协议,能够用来作signaling。服务端能够用 ejabberd andOpenfire实现。JavaScript客户端,例如 Strophe.js 使用BOSH去模仿双向通信流,但由于各类缘由,BOSH可能不像WebSocket那么有效率。(Jingle 是一种支持视频和语音的XMPP扩展,WebRTC从libjingle库(Jingle的C++实现库)里使用了网络和传输组件 )
2.像 ZeroMQ(听说TokBox服务端使用了)、OpenMQ的开源库。
3.使用支持WebSocket商业的云服务平台。
4.商业的WebRTC 平台,好比vLine.
开发者Phil Leggetter提供了一系列信息服务器和第三方库列表在Real-Time Web Technologies Guide。
用Node开发基于Sockket.io的信令服务
下面有个例子,Socket.io能够轻易建立一个用于交换信息的服务。
Socket.io很是适合WebRTC 的信令,由于它就是以“rooms”的概念设计的。
这个demo不是一个产品级别的服务,可是可以应付小数量的用户。
Socket.io经过下面的回调使用WebSocket: Adobe Flash Socket, AJAX long polling, AJAX multipart streaming, Forever Iframe and JSONP polling。
Socket.io也被移植到后端版本,可是最广为人知的是Node版本。
这个demo没有WebRTC,它只是展现怎么建立一个webapp的signaling。
用控制台查看log,去看下客户端加入一个房间和交换数据发生了什么变化。
WebRTC codelab会一步一步教你怎么整合这个demo变成一个完整的WEbRTC视频聊天应用。
你能够从step 5 of the codelab repo下载源码或者在samdutton-nodertc.jit.su运行(用两个浏览器打开这个连接 )
这是客户端的index.htl:

还有JavaScript文件main.js:

完整的服务端:css
- var static = require('node-static');
- var http = require('http');
- var file = new(static.Server)();
- var app = http.createServer(function (req, res) {
- file.serve(req, res);
- }).listen(2013);
- var io = require('socket.io').listen(app);
- io.sockets.on('connection', function (socket){
- // convenience function to log server messages to the client
- function log(){
- var array = ['>>> Message from server: '];
- for (var i = 0; i < arguments.length; i++) {
- array.push(arguments[i]);
- }
- socket.emit('log', array);
- }
- socket.on('message', function (message) {
- log('Got message:', message);
- // for a real app, would be room only (not broadcast)
- socket.broadcast.emit('message', message);
- });
- socket.on('create or join', function (room) {
- var numClients = io.sockets.clients(room).length;
- log('Room ' + room + ' has ' + numClients + ' client(s)');
- log('Request to create or join room ' + room);
- if (numClients === 0){
- socket.join(room);
- socket.emit('created', room);
- } else if (numClients === 1) {
- io.sockets.in(room).emit('join', room);
- socket.join(room);
- socket.emit('joined', room);
- } else { // max two clients
- socket.emit('full', room);
- }
- socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
- socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room);
- });
- });
复制代码
要运行这个app,你须要安装Node, socket.io and node-static。能够在 nodejs.org下载Node,再安装 socket.io 和node-static,在终端运行Node Package Manager:
npm install socket.ionpm install node-static
启动服务,运行下面命令
node server.js
在浏览器打开 localhost:2013.用新的浏览器打开localhost:2013 ,用控制台看下发生了什么
使用 RTCDataChannel交换信息
初始化一个WebRTC会话,必须有一个信令 服务器。
然而,一旦两端肯定了 一个通话,理论上,RTCDataChannel能够接替信令通道,这能够减小信号的延迟。
一旦信息直接在两端通信,RTCDataChannel会帮忙减小带宽使用和进程开销。没有例子,但能够看下面:
信令性能和扩展性
1.RTCPeerConnection 不会搜集candidates,直到setLocalDescription() 被调用。这个被JSEP IETF draft.强制要求了。
2.利用Trickle ICE(看上面解释):接收到candidates后当即调用addIceCandidate(),
现成的信令服务
这里有一些能够用的WebRTC signaling服务端:
html
若是你一点都不想编码,你能够用完整的商业WebRTC平台,像vLine, OpenTok and Asterisk
爱立信建立了一个 signaling server using PHP on Apache,在WebRTC早期的时候,如今这个已经被弃用了,可是若是你考虑到类似的状况,这个代码仍是值得一看的。
六.Signaling安全 html5
Security is the art of making nothing happen.
全部WebRTC 组件都被强制加密。
可是信令机制没有被WebRTC标准定义,全部确保信令安全取决于你,若是一个攻击者想去劫持信令,他们会致使会话停止,重定向连接和记录,改变或者注入内容。
一个牢固的信令最重要的功能是使用加密协议,HTTPS 和WSS (i.e TLS)能够确保信息不会非加密拦截。
同时,当心不要广播信令信息,否则攻击者可使用相同的信令服务连接其余来电用户。
信令交互完以后,使用ICE去处理NATs和防火墙
对于元数据的信令,WebRTC应用可使用中间服务,但实际的媒体和数据流在一个会话确立后,RTCPeerConnection 尝试去直连客户端:P2P
在一个简单的世界里,每个WebRTC端都有一个惟一的地址,这样他能够与其余端交换数据,以便直接 通信。

实际状况下,大多数设备都在一个或多个NAT层后面,有些有防毒软件阻碍肯定的端口和协议,还有不少在代理和公司的防火墙后面。
防火墙和NAT实际上可能由一些相似家庭wifi路由器产生的。

WebRTC 可使用ICE框架去克服真实世界的复杂网络。
为了实现这个功能,你的应用必须传ICE服务地址给RTCPeerConnection,以下所述。
ICE 试着寻找最佳路线去链接对方,它会并行的寻找全部可能性,而后选择最有效的可行方式。
ICE首先会尝试用设备系统或网卡获取到的主机地址去创建链接;若是这个失败了(设备在NATs后面就会)ICE从STUN服务器得到外部的地址,若是这个也失败了,就用TURN中转服务器作通信。
java
也就是说:
STUN服务器用来获取外部网络地址。
若是P2P失败的话,[size=14.4444446563721px]TURN服务器用来中继通信。
每个TURN服务器都支持STUN:一个TURN服务器是由一个STUN服务器加上中继功能。ICE也能够用来应付复杂的NAT设置:
事实上,NAT的”打洞“可能须要除了公共IP以外的端口地址。
WebRTC应用在iceServers配置对象(RTCPeerConnection constructor)里设置STUN and/or TURN服务器地址。
- {
- 'iceServers': [
- {
- 'url': 'stun:stun.l.google.com:19302'
- },
- {
- 'url': 'turn:192.158.29.39:3478?transport=udp',
- 'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
- 'username': '28224511:1379330808'
- },
- {
- 'url': 'turn:192.158.29.39:3478?transport=tcp',
- 'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
- 'username': '28224511:1379330808'
- }
- ]
- }
复制代码
一旦RTCPeerConnection 有了这些信息,ICE会自动启动:RTCPeerConnection 使用ICE框架计算出两端间最佳路线,须要STUN和TURN服务器。
STUNNATs会给它的设备提供一个内部网络IP地址,但这个地址不能在外网使用,由于没有外网的地址,全部WebRTC没办法作链接,为解决这个问题,WebRTC使用了STUN。
STUN服务架设在外网,它有一个简单的任务:获取一个发送请求的设备(运行在NAT后边的应用)的IP和端口,而后返回这个地址。换句话说,应用使用STUN服务器发现它的外网IP和端口,这个过程确保了一个WebRTC端得到它本身的公共地址,而后经过signaling机制发送这个信息给另外一端,这样就能够创建起一个直接链接。(在实际中,不一样的NATs有不一样的工做方式,可能有多个NAT层,可是原理是同样的)
STUN服务器不须要作太多工做和存储太多东西,因此简单的STUN服务器能够应付大量的请求。
根据 webrtcstats.com的统计,使用STUN方式创建WebRTC通话的成功率有86%的。

TURN
RTCPeerConnection 会试着用UDP在两端创建一个直连,若是失败了,RTCPeerConnection 会改用TCP,若是这个再失败了,TURN服务器会被做为后备方案使用,在两端间中继数据。
node
重述:TURN是在两端间中转视频/语音/数据 流,而不是发送数据。
TURN 有个公共地址,因此每一个端即便在防火墙或者代理后面,也能访问到。
TURN有个简单的任务,中转数据流,但不像STUN,TURN会花费大量带宽。全部,TURN须要够强壮。
图表表示TURN的做用:单纯的STUN不起做用,客户端就会转向使用TURN。
部署 STUN 和 TURN 服务器
做为测试,谷歌公布了一个公共的STUN服务,stun.l.google.com:19302, apprtc.appspot.com用的就是这个。
做为一个产品级别的 STUN/TURN服务器,咱们建议使用 rfc5766-turn-server,STUN 和TURN的源码能够从code.google.com/p/rfc5766-turn-server获取,这个连接也包括了部署的资料。A VM image for Amazon Web Services is also available.
本社区也发布了部署教程:部署教程
一个可代替的TURN服务器是restrund,能够在source code 下载到,下面介绍在谷歌Compute Engine部署resrund的步骤:git
- Open firewall as necessary, for tcp=443, udp/tcp=3478
- Create four instances, one for each public IP, Standard Ubuntu 12.06 image
- Set up local firewall config (allow ANY from ANY)
- Install tools:
sudo apt-get install make
sudo apt-get install gcc
- Install libre from creytiv.com/re.html
- Fetch restund from creytiv.com/restund.html and unpack
- wget hancke.name/restund-auth.patch and apply with patch -p1 < restund-auth.patch
- Run make, sudo make install for libre and restund
- Adapt restund.conf to your needs (replace IP addresses and make sure it contains the same shared secret) and copy to /etc
- Copy restund/etc/restund to /etc/init.d/
- Configure restund:
Set LD_LIBRARY_PATH
Copy restund.conf to /etc/restund.conf
Set restund.conf to use the right 10. IP address
- Run restund
- Test using stund client from remote machine: ./client IP:port
七.突破p2p:多人会议WebRTC
你能够须要看下Justin Uberti提议的IETF标识:请求TURN服务的API
很容易想象到一些场景不仅是一对一的视频通话,举个例子,公司小组须要一个视频会议,或者一个公开的演讲,一个演讲者面对数百(或者数千)的观看者。
一个WebRTC应用可使用多个RTCPeerConnections,这样每个端能够链接其余端造成一个网络。
talky.io就是使用这种方法实现,对于少数的用户,能够很好的工做。可是进程和带宽开销会很是大,特别是移动客户端。

在一个星型结构里,一个WebRTC客户端能够选择一个端去分布数据流给全部的用户,你能够本身设计从新分配机制的服务和构造区实现这种方式(werrtc.org提供了一个样例sample client application)
从Chrome31和Opera18 开始,从一个RTCPeerConnection 获取的媒体流,能够做为对方的输入:这里有个demosimpl.info/multi。这样能够确保更灵活的结构,由于它能够容许web应用经过选择哪一个用户能够链接去控制一个通话 路由。
多点控制部件MCU(Multipoint Control Unit)github
大量用户通话的更好解决方案是使用Multipoint Control Unit(MCU)。这是一个在大量参与者间分布媒体的桥接服务器。MCUs能够在一个视频 会议里处理不一样的分辨率,编解码,和帧速率。对于多端会议,有不少因素要考虑:最重要的是,从多个源里,怎么显示多个视频和混合音频。像 vLine 的云平台也在致力于优化传输路由。
你能够去买一个MCU硬件或者本身搭一个。

Cisco MCU背部
有几个开源的MCU硬件款能够选,例如 Licode(之前称为Lynckia) 生产的开源 MCU for WebRTC; OpenTok 平台的Mantis.
突破浏览器: VoIP, telephones 和 messaging
WebRTC 的标准让浏览器和不一样设备不一样平台,例如手机或者一个视频会议系统,进行通话称为可能。
SIP是一种信令协议,用来作VoIP和视频系统。为了让WebRTC和SIP端通信,WebRTC须要一个代理服务器去调解信令。信令必定会通过网关,可是一旦会话创建,视频和语音就能在两端传输。
PSTN,公共电话交换网络,是旧式模拟电话的交换网络。为了WebRTC和电话进行通话,必须经过一个PSTN网关。
同理,要让WebRTC跟Jingle端(像IM客户端)通信,须要一个中间XMPP服务器。
Jingle做为XMPP的扩展,用来实现视频和语音可以做为信息服务:如今的WebRTC就是基于C++实现libjingle 库发展来的,Jingle最初是Google Talk的技术。
一堆应用库,平台让WebRTC能在实际中通信:
web
sipML5 的开发者也开发了webrtc2sip的网关
Tethr and Tropo have demonstrated a framework for disaster communications 'in a briefcase', using an OpenBTS cell to enable communications between feature phones and computers via WebRTC. Telephone communication without a carrier!
发现更多
WebRTC codelab: 一步一步教你怎么打造一个视频和文本聊天应用,使用Socket.io Signaling服务。在Node上面跑。
2013 Google I/O WebRTC presentation WebRTC领头人Justin Uberti.chrome
WebRTC chapter 深刻研究WebRTC的结构,使用案例和性能。
!WebRTC中文社区原创,转载请注明出处,谢谢,水平有限,部分意思可能不到位,建议参考英文原帖!
WebRTC 能够p2p视频通话
可是...
WebRTC 仍然须要几个服务:
- 信令服务: 使客户端之间交换数据用来协调创建通话
- NAT穿透服务:应付NATs和防火墙
这篇文章会教你怎么搭建信令服务,和用STUN/TURN服务去作nat穿透。另外,咱们会解释WebRTC是怎么作到多端通话的。以及如何和VoIP/PSTN(电话)创建通话。
一.什么是信令服务(Signaling)?
信令是一个协调沟通的过程,为了让一个WebRTC应用发起一个“通话”,客户端间须要交换如下信令信息:
1.发起和关闭一个通话的控制信息;
2.错误信息;
3.媒体元数据,好比编码解码设置,带宽和媒体类型;
4.Key数据,用于确保安全通信;
5.网络数据,好比主机在外网下的IP地址和端口。
客户端的信令处理须要一种来回传递信息的方法,这种机制没有被WebRTC定义,你须要本身去建立它。下面咱们将描绘几种构建信令服务的方法。在此以前,先讲几个概念……
为何WebRTC没有定义信令?
为了不冗余和最大化兼容已经确立的技术,WebRTC没有指定信令的方法和协议。
-------------------------------
(WebRTC设计思想是彻底指定和控制媒体层,可是让signaling层尽可能脱离应用,缘由是不一样的应用可能会使用不一样的协议,好比已经存在的SIP或者Jingle呼叫协议等。这份协议中,须要交换的关键信息是多媒体会议的描述信息,包括在媒体层肯定必要的传输方式和 媒体配置信息)
------------------------------------------------
JSEP的结构一样避免了让浏览器保存状态信息,若是让浏览器成为一个保存信令状态的机器,会出现一个问题,就是每次当页面重载的时候,信令会丢失。因此更好的方案是用服务器保存信令状态。
JSEP协议要求端对端之间须要发起(offer)和回应(answer)上面提到的数据。
offer和answer用SDP(Session Description Protocol format信令描述协议格式)交流,像这样:
若是想知道全部的SDP表明的意思,能够看下这个连接:IEFT examples
记住,WebRTC这样设计是为了让offer端和answer端可以在tweaked以前经过SDP文档设置好参数。
举个例子: apprtc.appspot.com 里的preferAudioCodec()方法用来设置默认的编解码方式和比特率,SDP用JavaScript比较难操做,将来的版本可能会用JSON代替,可是SDP仍是有一些优点的。
二.RTCPeerConnection + 信令: offer,answer和candidate
RTCPeerConnection 是WebRTC客户端在两端创建音视频通信的API。
初始化RTCPeerConnection进程须要两个步骤:
1.肯定当期的媒体条件,例如分辨率,编解码能力。这些是给offer和answer的原始数据。
2.得到应用主机的网络地址(也就是candidate)
一旦这些本地数据被肯定好了,就必须经过信令机制在不一样端交换。
假设A想呼叫B,下面是整个offer/answer机制的细节:
1.A建立一个RTCPeerConnection对象。
2.A用RTCPeerConnection的createOffer()方法建立一个offer(用SDP协议描述)。
3.A用他的offer设置本地描述setLocalDescription()。
4.A序列化offer,而且用信令机制发送给B.
5.B用A的offer调用setRemoteDescription()设置对方的描述,B的RTCPeerConnection就知道了A的配置了。
6.B调用createAnswer(),若是成功会返回一个本地的session描述,既B的answer。
7.B用她的answer设置为本地的描述,经过调用setLocalDescription().设置本地描述
8.B用信令机制发送序列化后的answer给A。
9.A设置B的answer为对方session描述,经过调用setRemoteDescription()设置对方的描述.
(至此,A和B都设置了本地和对方的描述)
A和B还须要交换网络信息。'finding candidates' 指的是用ICE framework.去发现网络接口和端口。
1.A用一个onIceCandidate handler建立一个RTCPeerConnection对象。
2.当网络candidates有效时这个handler会被调用。
3.在这个handler里,A发送序列化的candidates数据给B,经过信令通道。
4.当B从A得到一个candidate信息,她调用addIceCandidate()去给对方描述添加candidate。
JSEP支持
ICE Candidate Trickling技术(容许呼叫者在首次初始化offer后,逐次发送candidates给被呼叫者,这是为了让被呼叫者开始设置链接而不用等到所有的candidates到达)
WebRTC 的信令编码
下面是W3C code exampleW3C代码样例,概况了完整的signaling过程。
样例假设已经有了信令机制:SignalingChannel。Signaling 会在下面探讨比较多的细节。
- var signalingChannel = new SignalingChannel();
- var configuration = {
- 'iceServers': [{
- 'url': 'stun:stun.example.org'
- }]
- };
- var pc;
- // call start() to initiate
- function start() {
- pc = new RTCPeerConnection(configuration);
- // send any ice candidates to the other peer
- pc.onicecandidate = function (evt) {
- if (evt.candidate)
- signalingChannel.send(JSON.stringify({
- 'candidate': evt.candidate
- }));
- };
- // let the 'negotiationneeded' event trigger offer generation
- pc.onnegotiationneeded = function () {
- pc.createOffer(localDescCreated, logError);
- }
- // once remote stream arrives, show it in the remote video element
- pc.onaddstream = function (evt) {
- remoteView.src = URL.createObjectURL(evt.stream);
- };
- // get a local stream, show it in a self-view and add it to be sent
- navigator.getUserMedia({
- 'audio': true,
- 'video': true
- }, function (stream) {
- selfView.src = URL.createObjectURL(stream);
- pc.addStream(stream);
- }, logError);
- }
- function localDescCreated(desc) {
- pc.setLocalDescription(desc, function () {
- signalingChannel.send(JSON.stringify({
- 'sdp': pc.localDescription
- }));
- }, logError);
- }
- signalingChannel.onmessage = function (evt) {
- if (!pc)
- start();
- var message = JSON.parse(evt.data);
- if (message.sdp)
- pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
- // if we received an offer, we need to answer
- if (pc.remoteDescription.type == 'offer')
- pc.createAnswer(localDescCreated, logError);
- }, logError);
- else
- pc.addIceCandidate(new RTCIceCandidate(message.candidate));
- };
- function logError(error) {
- log(error.name + ': ' + error.message);
- }
复制代码
查看“单页面”视频聊天的例子simpl.info/pc.能够在 控制台的lgo看到offer/answer 和candidate 的交换过程。
若是你想了解更多,能够在Chrome浏览器打开 chrome://webrtc-internals 或在opera打开 opera://webrtc-internals下载完整的代码。
三.成员发现机制(Peer discovery)
这里有个问题: 我怎么发现谁能够通话?
对于电话,咱们有电话号码和目录。对于在线视频聊天,咱们须要身份和业务管理系统和一种让用户开始会话的手段。
WebRTC apps须要一种 让客户端标示本身以即可以开始和加入会话的方法。
成员发现机制Peer discovery mechanisms没有被WebRTC定义,在这里咱们不用作选择。
这个过程能够像发送一个URL地址这么简单,对于视频聊天应用,好比 talky.io, tawk.com and browsermeeting.com,你经过分享一个通用连接邀请别人进入一个会话。
开发者Chris Ball开发了一个有趣的实验:serverless-webrtc,可让WebRTC呼叫参与者分享元数据,经过任何信息服务,好比IM,email或者信鸽。
四.怎么建立一个signling服务?
再说一遍:
信令机制没有被WebRTC标准定义,不管你选择哪一种 ,你须要一个中间服务器去交换信令信息和不一样客户端间的应用数据。
庆幸的是,信令信息很小,大部分交换都是在通话开始的时候。
在测试 apprtc.appspot.com 和 samdutton-nodertc.jit.su 时,咱们发现一个 视频会话,总共有大概30-45的信息被信令服务器处理,信息大小大概是10kB。
除了相对要求不高的带宽,WebRTC 信令服务器不用花费过多的内存和进程,由于只须要转发信息和保持不多的会议状态数据(好比那个客户端被链接了)
小贴士 :
信令机制不只能够用来交换会话元数据,也能用来传达应用数据。它就是个信息服务。
五.从服务端推信息给客户端
一个信令服务器须要是双向的:客户端到服务器和服务器到客户端。
双向通信违反了HTTP 客户端/服务端 请求/回复的模式,可是有一些发展多年的技术,例如long polling(长时间轮询) 被用来从服务端发送数据给一个运行中的web应用。
最近,EventSource API 被普遍的应用,它容许“服务端发送事件”:数据经过HTTP从服务端发送给浏览器。
这里有个简单的demo:simpl.info/es。
EventSource被设计为一种消息传送方式,可是它能够跟XHR 结合作成一个交换signaling的服务:从一个呼叫者传递信息,由XHR 请求传递,推送给被呼叫者。
WebSocket 是一种更天然的解放方案,它是为了全双工 客户端-服务端通信设计的(信息能够在同一时间在两个端传递)。
用纯WebSocket或者Server-Sent Events (EventSource) 作为signaling服务的优势是后端调用这些APIs能够用多种Web框架实现,在使用PHP,Python和Ruby的状况下。
大约有四分之三的浏览器支持WebSocket ,更重要的是,全部支持WebRTC的桌面浏览器和移动浏览器都支持WebSocket。
TLS(安全传输层协议)应该用于全部的连接,已确保信息不会被截断。
同时用proxy traversal减小问题(更多关于WebSocket 和proxy traversal的资料能够看WebRTC chapter 和WebSocket Cheat Sheet)
apprtc.appspot.com 的信令是经过Google App Engine Channel API完成的,Google App Engine Channel API是使用了Comet技术(长时间轮询)让APP后端和web客户端 实现推送通信功能。这里有个代码预演。
另一种方案,能够经过Ajax去轮询服务端获取signaling,但会致使一堆多余的网络请求,特别是在移动客户端。
在一个会话被肯定后,用户仍然须要去轮询signaling信息,由于会话可能会被其余用户改变或者终止。
《WebRTC》这本书就用了这种通过优化轮询频率的方法。
信令压缩
虽然一个信令服务器在每个客户端中花费至关小的带宽和CPU,可是一个广泛使用的应用可能须要从不一样的地点处理不少信息,而且有不少高的并发数。一个大流量的WebRTC 应用须要心理服务端去处理至关大的负荷。
这里咱们不讲细节,下面有一些 处理高数据量,高性能的信息通信设置:
1.XMPP,最初被称为Jabber:一种被开发用来即时通信的协议,能够用来作signaling。服务端能够用 ejabberd andOpenfire实现。JavaScript客户端,例如 Strophe.js 使用BOSH去模仿双向通信流,但由于各类缘由,BOSH可能不像WebSocket那么有效率。(Jingle 是一种支持视频和语音的XMPP扩展,WebRTC从libjingle库(Jingle的C++实现库)里使用了网络和传输组件 )
2.像 ZeroMQ(听说TokBox服务端使用了)、OpenMQ的开源库。
3.使用支持WebSocket商业的云服务平台。
4.商业的WebRTC 平台,好比vLine.
开发者Phil Leggetter提供了一系列信息服务器和第三方库列表在Real-Time Web Technologies Guide。
用Node开发基于Sockket.io的信令服务
下面有个例子,Socket.io能够轻易建立一个用于交换信息的服务。
Socket.io很是适合WebRTC 的信令,由于它就是以“rooms”的概念设计的。
这个demo不是一个产品级别的服务,可是可以应付小数量的用户。
Socket.io经过下面的回调使用WebSocket: Adobe Flash Socket, AJAX long polling, AJAX multipart streaming, Forever Iframe and JSONP polling。
Socket.io也被移植到后端版本,可是最广为人知的是Node版本。
这个demo没有WebRTC,它只是展现怎么建立一个webapp的signaling。
用控制台查看log,去看下客户端加入一个房间和交换数据发生了什么变化。
WebRTC codelab会一步一步教你怎么整合这个demo变成一个完整的WEbRTC视频聊天应用。
你能够从step 5 of the codelab repo下载源码或者在samdutton-nodertc.jit.su运行(用两个浏览器打开这个连接 )
这是客户端的index.htl:
还有JavaScript文件main.js:
完整的服务端:
- var static = require('node-static');
- var http = require('http');
- var file = new(static.Server)();
- var app = http.createServer(function (req, res) {
- file.serve(req, res);
- }).listen(2013);
- var io = require('socket.io').listen(app);
- io.sockets.on('connection', function (socket){
- // convenience function to log server messages to the client
- function log(){
- var array = ['>>> Message from server: '];
- for (var i = 0; i < arguments.length; i++) {
- array.push(arguments[i]);
- }
- socket.emit('log', array);
- }
- socket.on('message', function (message) {
- log('Got message:', message);
- // for a real app, would be room only (not broadcast)
- socket.broadcast.emit('message', message);
- });
- socket.on('create or join', function (room) {
- var numClients = io.sockets.clients(room).length;
- log('Room ' + room + ' has ' + numClients + ' client(s)');
- log('Request to create or join room ' + room);
- if (numClients === 0){
- socket.join(room);
- socket.emit('created', room);
- } else if (numClients === 1) {
- io.sockets.in(room).emit('join', room);
- socket.join(room);
- socket.emit('joined', room);
- } else { // max two clients
- socket.emit('full', room);
- }
- socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
- socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room);
- });
- });
复制代码
要运行这个app,你须要安装Node, socket.io and node-static。能够在 nodejs.org下载Node,再安装 socket.io 和node-static,在终端运行Node Package Manager:
npm install socket.ionpm install node-static
启动服务,运行下面命令
node server.js
在浏览器打开 localhost:2013.用新的浏览器打开localhost:2013 ,用控制台看下发生了什么
使用 RTCDataChannel交换信息
初始化一个WebRTC会话,必须有一个信令 服务器。
然而,一旦两端肯定了 一个通话,理论上,RTCDataChannel能够接替信令通道,这能够减小信号的延迟。
一旦信息直接在两端通信,RTCDataChannel会帮忙减小带宽使用和进程开销。没有例子,但能够看下面:
信令性能和扩展性
1.RTCPeerConnection 不会搜集candidates,直到setLocalDescription() 被调用。这个被JSEP IETF draft.强制要求了。
2.利用Trickle ICE(看上面解释):接收到candidates后当即调用addIceCandidate(),
现成的信令服务
这里有一些能够用的WebRTC signaling服务端:
若是你一点都不想编码,你能够用完整的商业WebRTC平台,像vLine, OpenTok and Asterisk
爱立信建立了一个 signaling server using PHP on Apache,在WebRTC早期的时候,如今这个已经被弃用了,可是若是你考虑到类似的状况,这个代码仍是值得一看的。
六.Signaling安全
Security is the art of making nothing happen.
全部WebRTC 组件都被强制加密。
可是信令机制没有被WebRTC标准定义,全部确保信令安全取决于你,若是一个攻击者想去劫持信令,他们会致使会话停止,重定向连接和记录,改变或者注入内容。
一个牢固的信令最重要的功能是使用加密协议,HTTPS 和WSS (i.e TLS)能够确保信息不会非加密拦截。
同时,当心不要广播信令信息,否则攻击者可使用相同的信令服务连接其余来电用户。
信令交互完以后,使用ICE去处理NATs和防火墙
对于元数据的信令,WebRTC应用可使用中间服务,但实际的媒体和数据流在一个会话确立后,RTCPeerConnection 尝试去直连客户端:P2P
在一个简单的世界里,每个WebRTC端都有一个惟一的地址,这样他能够与其余端交换数据,以便直接 通信。

实际状况下,大多数设备都在一个或多个NAT层后面,有些有防毒软件阻碍肯定的端口和协议,还有不少在代理和公司的防火墙后面。
防火墙和NAT实际上可能由一些相似家庭wifi路由器产生的。

WebRTC 可使用ICE框架去克服真实世界的复杂网络。
为了实现这个功能,你的应用必须传ICE服务地址给RTCPeerConnection,以下所述。
ICE 试着寻找最佳路线去链接对方,它会并行的寻找全部可能性,而后选择最有效的可行方式。
ICE首先会尝试用设备系统或网卡获取到的主机地址去创建链接;若是这个失败了(设备在NATs后面就会)ICE从STUN服务器得到外部的地址,若是这个也失败了,就用TURN中转服务器作通信。
也就是说:
STUN服务器用来获取外部网络地址。
若是P2P失败的话,[size=14.4444446563721px]TURN服务器用来中继通信。
每个TURN服务器都支持STUN:一个TURN服务器是由一个STUN服务器加上中继功能。ICE也能够用来应付复杂的NAT设置:
事实上,NAT的”打洞“可能须要除了公共IP以外的端口地址。
WebRTC应用在iceServers配置对象(RTCPeerConnection constructor)里设置STUN and/or TURN服务器地址。
- {
- 'iceServers': [
- {
- 'url': 'stun:stun.l.google.com:19302'
- },
- {
- 'url': 'turn:192.158.29.39:3478?transport=udp',
- 'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
- 'username': '28224511:1379330808'
- },
- {
- 'url': 'turn:192.158.29.39:3478?transport=tcp',
- 'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
- 'username': '28224511:1379330808'
- }
- ]
- }
复制代码
一旦RTCPeerConnection 有了这些信息,ICE会自动启动:RTCPeerConnection 使用ICE框架计算出两端间最佳路线,须要STUN和TURN服务器。
STUN
NATs会给它的设备提供一个内部网络IP地址,但这个地址不能在外网使用,由于没有外网的地址,全部WebRTC没办法作链接,为解决这个问题,WebRTC使用了STUN。
STUN服务架设在外网,它有一个简单的任务:获取一个发送请求的设备(运行在NAT后边的应用)的IP和端口,而后返回这个地址。换句话说,应用使用STUN服务器发现它的外网IP和端口,这个过程确保了一个WebRTC端得到它本身的公共地址,而后经过signaling机制发送这个信息给另外一端,这样就能够创建起一个直接链接。(在实际中,不一样的NATs有不一样的工做方式,可能有多个NAT层,可是原理是同样的)
STUN服务器不须要作太多工做和存储太多东西,因此简单的STUN服务器能够应付大量的请求。
根据
webrtcstats.com
的统计,使用STUN方式创建WebRTC通话的成功率有86%的。
TURN
RTCPeerConnection 会试着用UDP在两端创建一个直连,若是失败了,
RTCPeerConnection
会改用TCP,若是这个再失败了,TURN服务器会被做为后备方案使用,在两端间中继数据。
重述:TURN是在两端间中转视频/语音/数据 流,而不是发送数据。
TURN 有个公共地址,因此每一个端即便在防火墙或者代理后面,也能访问到。
TURN有个简单的任务,中转数据流,但不像STUN,TURN会花费大量带宽。全部,TURN须要够强壮。
图表表示TURN的做用:单纯的STUN不起做用,客户端就会转向使用TURN。
部署 STUN 和 TURN 服务器
做为测试,谷歌公布了一个公共的STUN服务,
stun.l.google.com:19302,
apprtc.appspot.com
用的就是这个。
做为一个产品级别的
STUN/TURN服务器,咱们建议使用
rfc5766-turn-server,
STUN 和TURN的源码能够从
code.google.com/p/rfc5766-turn-server
获取,这个连接也包括了部署的资料。
A
VM image for Amazon Web Services
is also available.
本社区也发布了部署教程:部署教程
一个可代替的TURN服务器是restrund,能够在
source code
下载到,
下面介绍在谷歌Compute Engine部署resrund的步骤:
- Open firewall as necessary, for tcp=443, udp/tcp=3478
- Create four instances, one for each public IP, Standard Ubuntu 12.06 image
- Set up local firewall config (allow ANY from ANY)
- Install tools:
sudo apt-get install make
sudo apt-get install gcc
- Install libre from creytiv.com/re.html
- Fetch restund from creytiv.com/restund.html and unpack
- wget hancke.name/restund-auth.patch and apply with patch -p1 < restund-auth.patch
- Run make, sudo make install for libre and restund
- Adapt restund.conf to your needs (replace IP addresses and make sure it contains the same shared secret) and copy to /etc
- Copy restund/etc/restund to /etc/init.d/
- Configure restund:
Set LD_LIBRARY_PATH
Copy restund.conf to /etc/restund.conf
Set restund.conf to use the right 10. IP address
- Run restund
- Test using stund client from remote machine: ./client IP:port
七.突破p2p:多人会议WebRTC
你能够须要看下Justin Uberti提议的IETF标识:请求TURN服务的API
很容易想象到一些场景不仅是一对一的视频通话,举个例子,公司小组须要一个视频会议,或者一个公开的演讲,一个演讲者面对数百(或者数千)的观看者。
一个WebRTC应用可使用多个RTCPeerConnections,这样每个端能够链接其余端造成一个网络。
talky.io就是使用这种方法实现,对于少数的用户,能够很好的工做。可是进程和带宽开销会很是大,特别是移动客户端。

在一个星型结构里,一个WebRTC客户端能够选择一个端去分布数据流给全部的用户,你能够本身设计从新分配机制的服务和构造区实现这种方式(werrtc.org提供了一个样例sample client application)
从Chrome31和Opera18 开始,从一个RTCPeerConnection 获取的媒体流,能够做为对方的输入:这里有个demosimpl.info/multi。这样能够确保更灵活的结构,由于它能够容许web应用经过选择哪一个用户能够链接去控制一个通话 路由。
多点控制部件MCU(Multipoint Control Unit)
大量用户通话的更好解决方案是使用Multipoint Control Unit(MCU)。这是一个在大量参与者间分布媒体的桥接服务器。MCUs能够在一个视频 会议里处理不一样的分辨率,编解码,和帧速率。对于多端会议,有不少因素要考虑:最重要的是,从多个源里,怎么显示多个视频和混合音频。像 vLine 的云平台也在致力于优化传输路由。
你能够去买一个MCU硬件或者本身搭一个。
Cisco MCU背部
有几个开源的MCU硬件款能够选,例如 Licode(之前称为Lynckia) 生产的开源 MCU for WebRTC; OpenTok 平台的Mantis.
突破浏览器: VoIP, telephones 和 messaging
WebRTC 的标准让浏览器和不一样设备不一样平台,例如手机或者一个视频会议系统,进行通话称为可能。
SIP是一种信令协议,用来作VoIP和视频系统。为了让WebRTC和SIP端通信,WebRTC须要一个代理服务器去调解信令。信令必定会通过网关,可是一旦会话创建,视频和语音就能在两端传输。
PSTN,公共电话交换网络,是旧式模拟电话的交换网络。为了WebRTC和电话进行通话,必须经过一个PSTN网关。
同理,要让WebRTC跟Jingle端(像IM客户端)通信,须要一个中间XMPP服务器。
Jingle做为XMPP的扩展,用来实现视频和语音可以做为信息服务:如今的WebRTC就是基于C++实现libjingle 库发展来的,Jingle最初是Google Talk的技术。
一堆应用库,平台让WebRTC能在实际中通信:
sipML5 的开发者也开发了webrtc2sip的网关
Tethr and Tropo have demonstrated a framework for disaster communications 'in a briefcase', using an OpenBTS cell to enable communications between feature phones and computers via WebRTC. Telephone communication without a carrier!
发现更多
WebRTC codelab: 一步一步教你怎么打造一个视频和文本聊天应用,使用Socket.io Signaling服务。在Node上面跑。
2013 Google I/O WebRTC presentation WebRTC领头人Justin Uberti.