http://www.cnblogs.com/bktmkd/p/5100082.html
- 信令服务: 使客户端之间交换数据用来协调建立通话
- NAT穿透服务:应付NATs和防火墙
一.什么是信令服务(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.io: 第一个抽象库 for WebRTC.
- easyRTC: 一个完整的WebRTC包 a full-stack WebRTC package.
- Signalmaster:一个使用 SimpleWebRTCJavaScrip客户端库的signaling服务
如果你一点都不想编码,你可以用完整的商业WebRTC平台,像vLine, OpenTok and Asterisk
爱立信创建了一个 signaling server using PHP on Apache,在WebRTC早期的时候,现在这个已经被弃用了,但是如果你考虑到相似的情况,这个代码还是值得一看的。
六.Signaling安全
信令交互完之后,使用ICE去处理NATs和防火墙
对于元数据的信令,WebRTC应用可以使用中间服务,但实际的媒体和数据流在一个会话确立后,RTCPeerConnection 尝试去直连客户端:P2P
在一个简单的世界里,每一个WebRTC端都有一个唯一的地址,这样他可以与其他端交换数据,以便直接 通讯。
实际情况下,大多数设备都在一个或多个NAT层后面,有些有防毒软件阻碍确定的端口和协议,还有很多在代理和公司的防火墙后面。
防火墙和NAT实际上可能由一些类似家庭wifi路由器产生的。
WebRTC 可以使用ICE框架去克服真实世界的复杂网络。
为了实现这个功能,你的应用必须传ICE服务地址给RTCPeerConnection,如下所述。
ICE 试着寻找最佳路线去连接对方,它会并行的寻找所有可能性,然后选择最有效的可行方式。
ICE首先会尝试用设备系统或网卡获取到的主机地址去建立连接;如果这个失败了(设备在NATs后面就会)ICE从STUN服务器获得外部的地址,如果这个也失败了,就用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'
- }
- ]
- }
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服务器会被作为后备方案使用,在两端间中继数据。
部署 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)
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:一个 开源的 JavaScript SIP 客户端
- jsSIP: JavaScript SIP库
- Phono: 开源JavaScript phone API, 作为一个插件
- Zingaya: 一个嵌入式电话部件
- Twilio: 音频和信息
- Uberconference: 会议技术
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.
- 信令服务: 使客户端之间交换数据用来协调建立通话
- NAT穿透服务:应付NATs和防火墙
一.什么是信令服务(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.io: 第一个抽象库 for WebRTC.
- easyRTC: 一个完整的WebRTC包 a full-stack WebRTC package.
- Signalmaster:一个使用 SimpleWebRTCJavaScrip客户端库的signaling服务
爱立信创建了一个 signaling server using PHP on Apache,在WebRTC早期的时候,现在这个已经被弃用了,但是如果你考虑到相似的情况,这个代码还是值得一看的。
六.Signaling安全
信令交互完之后,使用ICE去处理NATs和防火墙
对于元数据的信令,WebRTC应用可以使用中间服务,但实际的媒体和数据流在一个会话确立后,RTCPeerConnection 尝试去直连客户端:P2P
在一个简单的世界里,每一个WebRTC端都有一个唯一的地址,这样他可以与其他端交换数据,以便直接 通讯。
实际情况下,大多数设备都在一个或多个NAT层后面,有些有防毒软件阻碍确定的端口和协议,还有很多在代理和公司的防火墙后面。
防火墙和NAT实际上可能由一些类似家庭wifi路由器产生的。
WebRTC 可以使用ICE框架去克服真实世界的复杂网络。
为了实现这个功能,你的应用必须传ICE服务地址给RTCPeerConnection,如下所述。
ICE 试着寻找最佳路线去连接对方,它会并行的寻找所有可能性,然后选择最有效的可行方式。
ICE首先会尝试用设备系统或网卡获取到的主机地址去建立连接;如果这个失败了(设备在NATs后面就会)ICE从STUN服务器获得外部的地址,如果这个也失败了,就用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'
- }
- ]
- }
STUN服务架设在外网,它有一个简单的任务:获取一个发送请求的设备(运行在NAT后边的应用)的IP和端口,然后返回这个地址。换句话说,应用使用STUN服务器发现它的外网IP和端口,这个过程确保了一个WebRTC端获得它自己的公共地址,然后通过signaling机制发送这个信息给另一端,这样就可以建立起一个直接连接。(在实际中,不同的NATs有不同的工作方式,可能有多个NAT层,但是原理是一样的)
STUN服务器不需要做太多工作和存储太多东西,所以简单的STUN服务器可以应付大量的请求。
根据 webrtcstats.com 的统计,使用STUN方式建立WebRTC通话的成功率有86%的。
TURN
RTCPeerConnection 会试着用UDP在两端建立一个直连,如果失败了, RTCPeerConnection 会改用TCP,如果这个再失败了,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)
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:一个 开源的 JavaScript SIP 客户端
- jsSIP: JavaScript SIP库
- Phono: 开源JavaScript phone API, 作为一个插件
- Zingaya: 一个嵌入式电话部件
- Twilio: 音频和信息
- Uberconference: 会议技术
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.