WebRTC使端到端能够通信。
但是…
WebRTC仍然需要服务器:
- 让客户端交换元数据来协调通信:这被称为信令(signaling)
- 处理网络地址转换(NATs)和防火墙
这篇文章将会展示如何搭建一个信令服务
在本文中,我们将向您展示如何构建信令服务,以及如何使用STUN和TURN服务器处理真实连接的难题。 我们还解释了WebRTC应用程序如何处理多方通话以及与VoIP和PSTN(又称电话)等服务进行交互。
什么是信令
信令是协调通信的过程。 为了使WebRTC应用程序能够建立一个“通话”,其客户端需要交换以下信息:
- 会话控制消息用于打开或关闭通信
- 错误消息
- 媒体元数据,如编解码器和编解码器设置,带宽和媒体类型
- 密钥数据,用于建立安全的连接
- 网络数据,如:外界看到的主机IP地址和端口
此信令过程需要一种方法让客户端来回传递消息。 WebRTC API不实现该机制:你需要自己构建它。 我们在下面描述了构建信令服务的一些方法。 首先,需要一点背景…
为什么信令不是由WebRTC定义的?
为了避免出现冗余,并最大限度地提高与已有技术的兼容性,WebRTC标准并没有规定信令方法和协议。JavaScript会话建立协议JSEP概述了这种方法:
WebRTC通话建立的思想是完全指定和控制媒体平面,但是尽可能将信令平面留给应用程序。其原理是,不同的应用程序可能更喜欢使用不同的协议,例如现有的SIP或Jingle呼叫信令协议,或者对于特定应用程序定制的东西,可能是针对新颖的用例。在这种方法中,需要交换的关键信息是多媒体会话描述,其指定了建立媒体平面所必需的传输和媒体配置信息。
JSEP的架构也避免了浏览器不得不保存状态,即作为一个信令状态机。如果信令数据在每次刷新页面的时候都会发生丢失,就会出现问题。相反,信令状态机可以保存在服务器上。
JSEP要求端之间交换offer和answer:上面提到的媒体元数据。offer和answer以会话描述协议(SDP)的格式传递,如下所示:
|
|
想知道所有这些SDP gobbledygook实际意味着什么? 看看IETF的例子。
需要记住的是,WebRTC被设计为在被设置为本地或者远端描述之前,通过编辑SDP文本中的值,可以扭转offer或者answer。例如,apprtc.tc中的preferAudioCodec()函数可用于设置默认编解码器和比特率。使用 JavaScript处理SDP有些困难,有一些关于WebRTC的未来版本中是否应该使用JSON的讨论,但是坚持使用SDP还是有一些优势的。
RTCPeerConnection + signaling: offer, answer and candidate
RTCPeerConnection是WebRTC应用程序用来创建端对端连接并传输音视频的API。
为初始化这个过程,RTCPeerConnection有两个工作要做:
- 确定本地媒体条件,如分辨率和编解码器功能。这是用于offer和answer机制的元数据。
- 获取应用程序主机的潜在网络地址,成为候选人(candidates)
一旦确定了本地数据,就必须通过信令机制与远端进行交换。
让我们假设一个场景:Alice正在尝试呼叫Eve。下面是完整的offer/answer机制:
- Alice创建一个RTCPeerConnection对象。
- Alice使用RTCPeerConnection createOffer()方法产生一个offer(一个SDP会话描述)。
- Alice用他的offer调用setLocalDescription()。
- Alice将offer字符串化,并使用信令机制将其发送给Eve。
- Eve用Alice的offer调用setRemoteDescription(),以便她的RTCPeerConnection知道Alice的设置。
- Eve调用createAnswer(),成功的回调是传入一个本地的会话描述:Eve的answer。
- Eve通过调用setLocalDescription()将其answer设置为本地描述。
- Eve然后使用信令机制将她的字符串化的answer发回给Alice。
- Alice使用setRemoteDescription()将Eve的应答设置为远程会话描述。
Alice和Eve也需要交换网络信息。“查找候选人(find candidate)”这个表达是指使用ICE框架查找网络接口和端口的过程。
- Alice使用onicecandidate handler创建一个RTCPeerConnection对象。
- handler在网络候选人变得可用时被调用。
- 在handler中,Alice通过他们的信令通道将字符串化的候选数据发送给Eve。
- 当Eve从Alice那里获得候选消息时,她调用addIceCandidate(),将候选项添加到远端描述中。
JSEP支持ICE Candidate Trickling,它允许主叫方(caller)在最初的offer之后递增地向被叫方提供候选项(candidates),并使被叫方开始在通话中进行操作并建立连接而不用等所有候选项到达。
WebRTC信令代码
下面的W3C代码示例总结了一个完整的信令过程。该代码假定存在一些信令机制,SignalingChannel。我们会在下文中更详细地讨论信令。
|
|
要查看实际offer/answer和候选项的交流过程,请参阅simpl.info/pc上的“单页”视频聊天示例的控制台日志。如果你想知道更多,请从Chrome的chrome://webrtc-internals页面或者Opera中的opera://webrtc-internals页面下载WebRTC 信令和统计数据的完整转储。
对端发现
这是“我该如何找到我要交谈的人”的一种高端说法。
对于电话来说,我们有电话号码和目录。对于在线视频聊天和消息发送,我们需要身份和状态管理系统,以及用户启动会话的方式。WebRTC应用程序需要一种方式让客户互相通知他们想要开始或加入一个通话。
端发现机制不是由WebRTC定义的。这个过程可以像发送电子邮件或发送一个URL一样简单:对于视频聊天应用程序,比如talky.io,tawk.com和browsermeeting.com,您可以通过共享自定义链接来邀请人们进行通话。开发人员Chris Ball已经搭建了一个有趣的无服务器的webrtc实验,使WebRTC呼叫参与者能够通过他们喜欢的任何消息服务来交换元数据。
我怎么才能建立一个信令服务?
重申:信令协议和机制不是由WebRTC标准定义的。无论你选择什么,你都需要一个中间服务器来在客户端之间交换信令消息和应用程序数据。不走运的是,一个网络应用程序不能简单地向互联网喊“连接到我朋友那!”
幸亏信令消息很小,而且大多在通话开始的时候进行交换。在使用appr.tc进行测试时,我们发现对于视频聊天会话,信令服务总共处理了30-45条消息,总共消息的大小大约为10KB。
WebRTC 信令业务在带宽方面的要求相对较低,因为它们只需要中继消息并保留少量的会话状态数据(如连接的客户端),同样不会消耗太多的处理或存储空间。
小贴士:用于交换会话元数据的信令机制也可用于传送应用程序数据。这只是一个消息服务而已!。
将消息从服务器推送到客户端
信令的消息服务需要是双向的:客户端到服务器和服务器到客户端。双向通信违背了HTTP客户端/服务器的请求/响应模型,但是为了将数据从运行在Web服务器上的服务推送到运行在浏览器中的Web应用程序,多年来已经开发了诸如长轮询)之类的各种hac
k方法。
最近,EventSource API已经得到了广泛的实现。这开启了 “服务器发送的事件”:通过HTTP从Web服务器发送到浏览器客户端的数据。在simpl.info/es上有一个简单的演示。EventSource是为单向消息传递而设计的,但是它可以和XHR结合使用来搭建交换信令消息的服务:信令服务器通过XHR请求传递来自呼叫者的消息,通过EventSource推送给被叫者。
WebSocket是一个更自然的解决方案,专为全双工客户端-服务器通信而设计(消息可以同时在两个方向上传输)。使用纯WebSocket或Server-Sent Events(EventSource)构建的信令服务的一个优点是这些API的后端可以在大多数Web托管软件包通用的各种Web框架上实现,比如PHP,Python和Ruby。
大约四分之三的浏览器都支持WebSocket,更重要的是,所有支持WebRTC的浏览器都支持WebSocket,无论是在台式机还是手机上。应该为所有连接都使用TLS,以确保消息不会因为没有加密而被截取,并且减少代理遍历的问题。(有关WebSocket和代理遍历的更多信息,请参阅Ilya Grigorik的高性能浏览器网络中的WebRTC章节。Peter Lubber的WebSocket备忘单提供了有关WebSocket客户端和服务器的更多信息)
用于标准的appr.tc WebRTC视频聊天应用程序的信令通过Google App Engine Channel API完成,该API使用Comet)技术(长轮询)来启用App Engine后端与Web客户端之间推送通信的信令传输。HTML5 Rocks WebRTC文章中详细介绍了该应用程序的代码演示。
也可以通过让WebRTC客户端通过Ajax轮询消息服务器来处理信令,但这回导致大量的冗余网络请求,尤其对于移动设备是有问题的。即使在会话建立之后,对端也需要轮询信令消息,以防其他端发生改变或终止会话。WebRTC Book app示例使用此选项,并对轮询频率进行了一些优化。
扩展信令
尽管信令服务消耗客户端带宽和CPU相对较少,但是一个很受欢迎的应用程序的信令服务器可能需要处理来自不同位置的大量消息,而且并发性较高。有大量流量的WebRTC应用程序需要能够处理相当大负载的信令服务器。
在这里我们不会对其进行详细讨论,但是对于大容量、高性能的消息传递有很多选择,包括:
- eXtensible Messaging and Presence Protocol(XMPP),最初是叫Jabber:一个为即时消息而开发的可用于信令的协议。服务器实现包括ejabberd和Openfire。Strophe.js等 JavaScript等客户端使用BOSH模拟双向流,但由于各种原因,BOSH可能不如WebSocket高效,同样的原因可能导致无法很好地扩展缩放。(跳离正题:Jingle是一个支持语音和视频的XMPP扩展;WebRTC项目使用来自libjingle库,一个Jingle的C++实现,的网络和传输组件。)
- 开源的库,如ZeroMQ(TokBox在他们的Rumor服务中使用它)和OpenMQ。NullMQ通过WebSocket使用STOMP协议将ZeroMQ概念应用于Web平台。
- 使用WebSocket的商业云消息平台(尽管可能会回退到长轮询),例如Pusher,Kaazing和PubNub。(PubNub也有WebRTC的API)
- 像vLine这样的商业WebRTC平台。
在Node上使用Socket.io构建信令服务
下面是一个简单的Web应用程序的代码,它使用Node上的Socket.io构建的信令服务。Socket.io的设计使构建服务、交换信息变得简单,而且因为它内置了“房间”的概念,Socket.io特别适用于WebRTC 信令。这个例子不是为了扩大产品级别的信令服务而设计的,但是对于相对较少的用户来说效果很好。
Socket.io使用带有回退的WebSocket:AJAX长轮询,AJAX多部分流,Forever Iframe和JSONP轮询。它已被移植到各种后端,但也许最有名的是它的Node版本,我们将在下面的例子中使用它。
在这个例子中没有WebRTC:它的设计目的只是为了展示如何在一个Web应用程序中构建信令。查看控制台日志以查看客户端加入房间并且交换消息时发生的情况。我们的WebRTC codelab提供了分步说明,解释了如何将这个例子集成到一个完整的WebRTC视频聊天应用程序。
下面是客户端, index.html:
|
|
以及客户端中引用的JavaScript文件main.js::
|
|
完整的服务端app:
|
|
(你不需要去学习node-static;它只是碰巧在这个例子里用到)
在localhost上运行这个程序,你需要安装Node,socket.io和node-static。Node可以从nodejs.org下载。要安装socket.io和node-static,请从你的应用程序目录中的终端运行Node Package Manager:
|
|
启动服务器,在应用目录下运行下面的命令:
|
|
在浏览器中打开localhost:2013。在任何浏览器中打开新的标签页或窗口,然后再次打开localhost:2013。如果想要查看发生了什么,请查看控制台:在Chrome和Opera中,可以通过Command-Option-J或Ctrl-Shift-J通过DevTools访问。
不管你选择使用什么方式进行信号传输,后端和客户端app都至少需要提供类似于此示例的服务。
信令陷阱
在setLocalDescription()被调用之前,RTCPeerConnection并不会开始收集候选:这是在JSEP IETF草案中规定的。
利用Trickle ICE(见上文):候选到达后立即调用addIceCandidate()。
现成的信令服务器
如果你不想自己做WebRTC 信令服务器,有一些现成的服务器可用,它们可以使用上面例子中使用Socket.io,并与WebRTC客户端 JavaScript库集成:
- webRTC.io:WebRTC最先出现的几个抽象库之一。
- easyRTC:完整的WebRTC包。
- Signalmaster:一个与SimpleWebRTC JavaScript客户端库一起使用的信号服务器。
如果你一点代码都不想写的话,还可以从像vLine,OpenTok和Asterisk等公司获得完整的商业WebRTC平台。
爱立信在WebRTC早期的时候建立了一个在Apache上使用PHP的信令服务器。虽说这现在已经过时了,但是如果你正在考虑类似的东西,那么还是值得看一下代码的。
信令安全
安全是无所作为的艺术。
— Salman Rushdie
加密对于所有WebRTC组件来说都是强制性的。
但是信令机制并不是由WebRTC标准定义的,所以保护信令安全的责任就全在你的身上了。如果攻击者设法劫持了信令,他们就可以停止会话,重新定向连接和记录,更改或注入其他内容。
确保信令安全的最重要因素是使用安全协议,即HTTPS和WSS(即TLS),确保消息不会因未加密而截获。另外要小心,不要以可以被其他呼叫方能够获取的方式用同一个信令服务器广播信令消息。
在信令之后:使用 ICE来对付NAT和防火墙
对于元数据信令,WebRTC应用程序使用中介服务器,但对于实际的媒体和数据流,一旦建立对话的话,RTCPeerConnection就会尝试点对点地直接连接客户端。
在简单的情况中,每个WebRTC端点都有一个唯一的地址,可以与其他端进行交换以便直接通信。
实际上大多数设备都是处在一层或者多层NAT之后的,其中有一些包含可以阻挡某些端口和协议的防病毒软件,还有很多设备是在代理和公司防火墙之后的。防火墙和NAT实际上可以由相同的设备实现,比如说家庭WiFi路由器。
WebRTC应用程序可以使用ICE框架来消除实际网络的复杂性。为了实现这一点,你的应用程序必须将 ICE服务器的URL传递给RTCPeerConnection,就像下面所描述的那样。
ICE试图找到连接对方的最佳途径。它会并行地尝试所有可能性,并选择最有效的选项。 ICE首先尝试使用从设备操作系统和网卡获取的主机地址进行连接;如果不成功的话(对于NAT后面的设备就会失败), ICE会使用 STUN服务器获取外部地址,如果还是失败的话,则通过 TURN中继服务器路由数据。
换句话说:
STUN服务器是用来获取外部地址的。
TURN服务器是用来在直接连接(点到点)失败的情况下进行中继数据流量的
每个 TURN服务器都支持 STUN: TURN服务器也是一个增加了内置中继功能的 STUN服务器。 ICE还可以应付NAT设置的复杂性:实际上,NAT“打孔”可能不仅仅需要一个公共IP:端口地址。
STUN 和/或 TURN服务器的URL(可选择地)由iceServers配置对象中的WebRTC应用程序指定,该配置对象是RTCPeerConnection构造函数的第一个参数。对于appr.tc来说,值看起来是这样的:
|
|
注意:上面显示的 TURN 证书是有时间限制的,在2013年9月到期。 TURN服务器运行起来很昂贵,你需要为自己的服务器付费或者找一个服务提供商。要测试证书,你可以使用候选收集样本,并检查是否获得了类型为中继的候选。
一旦RTCPeerConnection具有该信息, ICE的作用就会自动发生:RTCPeerConnection使用 ICE框架 来计算到对端之间的最佳路径,并根据需要使用 STUN和 TURN服务器。
STUN
NAT给设备提供了一个IP地址以使用专用局域网,但是这个地址不能在外部使用。由于没有公用地址,WebRTC端对端就无法进行通信。而WebRTC使用STUN来解决这个问题。
STUN服务器位于公共网络上,并且有一个简单的任务:检查传入请求的IP地址(来自运行在NAT后面的应用程序),并将该地址作为响应发送回去。换句话说,应用程序使用 STUN服务器从公共角度发现其IP:端口。这个过程使得WebRTC一端为自己获得一个可公开访问的地址,然后通过信令机制将其传递给另一端以建立直接连接。(实际上不同NAT工作方式都有所不同,可能有多个NAT层,但是原理是一样的)。
因为 STUN服务器不需要做太多的工作或者记特别多的东西,所以相对低规格的 STUN服务器就可以处理大量的请求。
根据webrtcstats.com的统计(2013年),大多数WebRTC通话都成功地使用 STUN进行连接,有86%。尽管对于防火墙之后的两端之间的呼叫以及复杂的NAT配置,成功通话量会更少一些。
TURN
RTCPeerConnection尝试通过UDP建立对等端之间的直接通信。如果失败的话,RTCPeerConnection就会使用TCP进行连接。如果使用TCP还失败的话,可以用 TURN服务器作为后备,在终端之间转发数据。
重申: TURN用于中继对等端之间的音频/视频/数据流,而不是信令数据。
TURN服务器具有公共地址,因此即使对等端位于防火墙或代理之后也可以与其他人联系。 TURN服务器有一个概念上来讲简单的任务—中继数据流—但是与 STUN服务器不同的是,他们会消耗大量的带宽。换句话说, TURN服务器需要更加的强大。
上图显示了 TURN的作用:单纯的 STUN没有成功建立连接,所以每个对等端还需要使用 TURN服务器。
部署 STUN和 TURN服务器
为了进行测试,Google运行了一个公共 STUN服务器 stun.l.google.com:19302,就是appr.tc所使用的那样。对于产品的 STUN/ TURN服务,我们推荐使用rfc5766-turn-server; STUN和 TURN服务器的源代码可从code.google.com/p/rfc5766-turn-server获得,该代码还提供了有关服务器安装的多个信息源的链接。Amazon Web Services的VM映像也可用。
另一个 TURN服务器是restund,提供源代码,也有AWS服务。以下是如何在Google Compute Engine上设置restund的说明。
- 根据需要打开防火墙,对于tcp = 443,udp/tcp = 3478
- 创建四个实例,每个公共IP标准一个,Standard Ubuntu 12.06映像
- 设置本地防火墙配置
安装工具
12sudo apt-get install makesudo apt-get install gcc从creytiv.com/re.html安装libre
- 从creytiv.com/restund.html获取并解压缩restund
- wget hancke.name/restund-auth.patch并且使用patch – p1
- 对libre和restund运行make, sudo make install
- 根据你的需要(替换IP地址并确保它包含相同的共享密钥)对restund.conf进行调整,并复制到/etc
- 复制restund/etc/restund到/etc/init.d/
- 配置restund:
- 设置LD_LIBRARY_PATH
- 复制restund.conf到/etc/restund.conf
- 设置restund.conf以使用正确的 10. IP地址
- 运行restund
- 从远端机上使用社团的客户端进行测试:./client IP:port
多方WebRTC
你可能还想查看一下Justin Uberti提出的用于访问TURN服务的REST API的IETF标准。
很容易想象媒体流的使用情况超出了简单的一对一呼叫:例如,一组同事之间的视频会议,或一个发言者和数百(数百万)个观众的公共事件。
WebRTC应用程序可以使用多个RTCPeerConnection,以便每个端点都可以连接到网格配置中的每个其他端点。这是talky.io等应用程序所采取的方法,对于只有少数几个对等端的情况来说可以很好的工作。除此之外,处理和带宽会过度消耗,对于移动客户端来说尤其是这样。
或者,WebRTC应用程序可以选择一个端点以星形配置将流分配给所有其他端点。也可以在服务器上运行WebRTC端点并构建自己的重新分配机制。(webrtc.org提供了一个客户端应用示例)
从Chrome 31和Opera 18开始,来自一个RTCPeerConnection的MediaStream可以用作另一个的输入:在simpl.info/multi上有一个演示。这可以启用更灵活的体系结构,因为它使Web应用程序能够通过选择要连接的其他对等端来处理呼叫路由。
多点控制单元
大量endpoint情况的更好选择是使用多点控制单元(Multipoint Control Unit,MCU)。它是一个服务器,可以作为在大量参与者之间分发媒体的桥。MCU可以处理视频会议中的不同分辨率,编解码器和帧速率,处理转码,选择性流转发,混音或录制音频和视频。对于多方通话,需要考虑许多问题:特别是如何显示多个视频输入并混合来自多个来源的音频。
你可以购买一个完整的MCU硬件包,或者建立自己的MCU。
有几个开源的MCU软件可供选择。比如说,Licode为WebRTC做了一个开源MCU;OpenTok也有Mantis。
Several open source MCU software options are available. For example, Licode (previously know as Lynckia) produces an open source MCU for WebRTC; OpenTok has Mantis.
除了浏览器以外还有:VoIP,电话和消息
WebRTC的标准化特性使得在浏览器中运行的WebRTC应用程序与另一个通信平台运行的设备或停牌(例如电话或视频会议系统)之间建立通信成为可能。
SIP是VoIP和视频会议系统使用的信令协议。为了实现WebRTC应用程序与视频会议系统等SIP客户端之间的通信,WebRTC需要代理服务器来调解信令。信令必须通过网关流动,但一旦通信建立,SRTP流量(视频和音频)就可以直接流向对等端。
公共交换电话网(PSTN)是所有“普通老式”模拟电话的电路交换网络。对于WebRTC应用程序和电话之间的通话,通信必须通过PSTN网关。同样,WebRTC应用程序需要中间的XMPP服务器来与Jingle端点(如IM客户端)进行通信。Jingle由Google开发,作为XMPP的扩展,为语音和视频提供消息传递服务:当前的WebRTC实现是基于C++ libjingle库的,这是一个最初为Google Talk开发的Jingle实现。
许多应用程序,库,和平台利用WebRTC与外部世界的沟通能力:sipML5,jsSIP,Phono,Zingaya,Twilio和Uberconference等等。
sipML5开发者也构建了webrtc2sip网关。Tethr和Tropo展示了一个在灾难通信框架,使用OpenBTS单元通过WebRTC实现手机和计算机之间的通信。这是一个没有运营商在中间的电话通信!