《Getting Started with WebRTC》第二章 WebRTC技术介绍

《Getting Started with WebRTC》第二章 WebRTC技术介绍

本章作WebRTC的技术介绍,主要讲以下的概念:
  .  如何建立P2P的通信
  .  有效的信令选项
  .  关键API的关系

2.1 设置通信

尽管WebRTC通信的基础是P2P的, 但设置这个通信的初始步骤是要求一些协作的。
这些动作通常由Web服务器和/或信令服务器提供。
这个协作可以允许两个或多个WebRTC设备或端找到彼此,交换通信的细节,
协商定义了他们如何通信的会话,
最后建立它们之间的直播P2P媒体流。

2.2 一般流程

应用场景其实是很多的,从简单的页面DEMO到多方会议。
这里只做最简单的流程:两个浏览器使用WebRTC建立一个最简单的视频通话。
下面是这个流程的总结:
  .  连接用户
  .  启动信令
  .  寻找候选者
  .  协商媒体会话
  .  启动 RTCPeerConnection流

2.3 连接用户

通信处理的第一步是通过某种方式连接两个用户。
最简单的方式让两个用户访问同个一网页,这个页面标识了每个浏览器的访问后,
然后通过两使用相同的信令服务器,如WebSocket API,来连接彼此。


这种类型的Web页面,通常都是给需要通信的浏览器分配一个唯一的标识符,
你可以认为这个标识符是一个房间号或会话ID。
当第一个用户访问网页时,会生成一个唯一的URL,这个唯一的URL就是ID号,
然后第一个用户将这个ID发送给第二个用户,
当这两个用户都打开了这个URL时,第一步就完成了。

2.4 启动信令

现在两个用户共享了同一个标识符,它们可以通过交换信令消息来协商建立他们的WebRTC连接。
在这里,信令消息可以是任何形式的通信,它用来帮助两浏览器建立并控制它们的WebRTC通信。
WebRTC标准并没有严格定义如何实现它。

实际上这是一个优点,因为它将这一部分放开,能带来创新和发展。
当然,这同样也是一个挑战,因为这种不确定会导致开发者不好区分谁是RTC通信的新用户。

前面的apprtc DEMO演示了使用XHR和Google AppEngine Channel API的组合使用:
(https://developers.google.com/appengine/docs/python/channel/overview). 
很容易的,这也可以是任何其它XHR轮循,服务器发送事件:
(http://www.html5rocks.com/en/tutorials/eventsource/basics/), 
WebSockets
(http://www.html5rocks.com/en/tutorials/websockets/basics/),
的组合使用。

2.5 寻找候选者

接下来的步骤是在两个浏览器间交换信息,包括它们的网络,及它们如何取得联系。
这个过程通常叫做“寻找候选者”,
当这一步结束后,浏览器能被映射到可直接访问的网络接口和端口。
每个浏览器都可能是位于一个路由器之后,
这个路由器可能使用了Network address Translation(NAT)来连接本地网络之后再连到互联网,
也可能它是被防火墙做了限制,比如阻塞了某些端口和输入连接。

找到一种方式去连接这些类型的路由器常用的方法是NAT Traversl(NAT穿越):
(http://en.wikipedia.org/wiki/NAT_traversal),
并且它对于建立WebRTC通信极为重要。

实现NAT穿越的一种常用方式是使用一个Session Traversal Utilities for NAT (STUN)服务器,
(http://en.wikipedia.org/wiki/Session_Traversal_Utilities_for_NAT),
它能很简单地帮助识别公网上的连接并返回有用的信息。
有很多人都提供了公开的STUN服务器,apprtc示例使用了Google提供的。

如果STUN服务器不能找到一种方式从公网上和你的浏览器建立连接,
那你就不得不回退到使用中转媒介,如Traversal Using Relay NAT (TURN) 服务器
(http://en.wikipedia.org/wiki/Traversal_Using_Relay_NAT).
来解决这个问题。
这样的做的副作用是把你回退到了非端到端的结构中,
但在某些情况下,你可能在非常特殊的受限的私有网络中,这可能是你自己的选择。

对于WebRTC来说,这整个过程通常绑定到单个的
InteractiveConnectivity Establishment (ICE) 框架中
 (http://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment),
它会按要求从STUN服务器回退到TURN服务器。

NOTE:
NAT穿越(NAT traversal)涉及TCP/IP网络中的一个常见问题,
即在处于使用了NAT设备的私有TCP/IP网络中的主机之间建立连接的问题.

概述:
会遇到这个问题的通常是那些客户端网络交互应用程序的开发人员,尤其是在对等网络和VoIP领域中。
IPsec VPN客户普遍使用NAT-T来达到使ESP包通过NAT的目的。
尽管有许多穿越NAT的技术,但没有一项是完美的,这是因为NAT的行为是非标准化的。
这些技术中的大多数都要求有一个公共服务器,
而且这个服务器 使用的是一个众所周知的、从全球任何地方都能访问得到的IP地址。
一些方法仅在建立连接时需要使用这个服务器,
而其它的方法则通过这个服务器中继所有的数据——这就引入了带宽开销的问题.


方法:
1. NAT/ALG 方式
普通NAT是通过修改UDP或TCP报文头部地址信息实现地址的转换,
但对于VOIP应用,在TCP/UDP净载中也需带地址信息,
ALG方式是指在私网中的VOIP终端在净载中填写的是其私网地址,
此地址信息在通过NAT时被修改为NAT上对外的地址。
语音和视频协议(H323、SIP、MGCP/H248)的识别和对NAT/Firewall的控制,
同时每增加一种新的应用都将需要对 NAT/Firewall进行升级。


在安全要求上还需要作一些折衷,
因为ALG 不能识别加密后的报文内容,所以必须保证报文采用明文传送,
这使得报文在公网中传送时有很大的安全隐患。
NAT/ALG是支持VOIP NAT穿透的一种最简单的方式,
但由于网络实际情况是已部署了大量的不支持此种特性的NAT/FW设备,
因此,实际应用中,很难采用这种方式。


2. MIDCOM 方式
与NAT/ALG不同的是,MIDCOM的基本框架是采用可信的第三方(MIDCOM Agent)对Middlebox (NAT/FW)进行控制,


VOIP协议的识别不由Middlebox完成,而是由外部的MIDCOM Agent完成,
因此VOIP使用的协议对Middlebox是透明的 .


由于识别应用协议的功能从Middlebox移到外部的MIDCOM Agent上,
根据MIDCOM 的构,在不需要更改Middlebox基本特性的基础上,
通过对MIDCOM Agent的升级就可以支持更多的新业务,这是相对NAT/ALG方式的一个很大的优势。


在VOIP实际应用中,Middlebox功能可驻留在NAT/Firewall,
通过软交换设备(即MIDCOM Agent)对IP语音和视频协议(H323、SIP、MGCP/H248)
的识别和对NAT/Firewall的控制,来完成VOIP应用穿越 NAT/Firewall .
在安全性上,MIDCOM方式可支持控制报文的加密,可支持媒体流的加密,因此安全性比较高。
如果在软交换设备上实现对SIP/H323/MGCP/H248协议的识别,
就只需在软交换和NAT/FW设备上增加MIDCOM协议即可,
而且以后新的应用业务识别随着软交换的支持而支持,
此方案是一种比较有前途的解决方案,但要求现有的NAT/FW设备需升级支持MIDCOM协议,
从这一点上来说,对已大量部署的NAT/FW设备来说,也是很困难的,同NAT/ALG方式有相同的问题。


3. STUN 方式
解决穿透NAT问题的另一思路是,私网中的VOIP终端通过某种机制预先得到出口NAT上的对外地址,
然后在净载中所填写的地址信息直接填写出口NAT上的对外地址,而不是私网内终端的私有IP地址,
这样净载中的内容在经过NAT时就无需被修改了,只需按普通NAT流程转换报文头的IP地址即可,
净载中的 IP地址信息和报文头地址信息是一致的。
STUN协议就是基于此思路来解决应用层地址的转换问题。


STUN的全称是Simple Traversal of UDP Through Network Address Translators,
即UDP对NAT的简单穿越方式。 
应用程序(即STUN CLIENT)向NAT外的STUN SERVER通过UDP发送请求STUN 消息,
STUN SERVER收到请求消息,产生响应消息,响应消息中携带请求消息的源端口,
即STUN CLIENT在NAT上对应的外部端口。然后响应消息通过NAT发送给STUN CLIENT,
STUN CLIENT通过响应消息体中的内容得知其NAT上的外部地址,
并将其填入以后呼叫协议的UDP负载中,告知对端,本端的RTP接收地址和端口号为NAT 外部的地址和端口号。


由于通过STUN协议已在NAT上预先建立媒体流的NAT映射表项,故媒体流可顺利穿越NAT.
STUN协议最大的优点是无需现有NAT/FW设备做任何改动。
由于实际应用中,已有大量的NAT/FW,并且这些NAT/FW并不支持VoIP的应用,
如果用MIDCOM或NAT/ALG方式来解决此问题,需要替换现有的NAT/FW,这是不太容易的。
而采用STUN方式无需改动NAT/FW,这是其最大优势,同时STUN方式可在多个NAT串联的网络环境中使用,
但MIDCOM方式则无法实现对多级NAT的有效控制。
STUN的局限性在于需要VOIP终端支持STUN CLIENT的功能,
同时STUN并不适合支持TCP连接的穿越,因此不支持H323.
另外STUN方式不支持对防火墙的穿越,不支持对称NAT (Symmetric NAT)类型
(在安全性要求较高的企业网中,出口NAT通常是这种类型)穿越。


4. TURN方式
TURN方式解决NAT问题的思路与STUN相似,
也是私网中的VOIP终端通过某种机制预先得公网上的服务地址(STUN方式得到的地址为出口NAT上外部地址,
TURN方式得到地址为TURN Server上的公网地址),
然后在报文净载中所要求的地址信息就直接填写该公网地址。


TURN的全称为Traversal Using Relay NAT,即通过Relay方式穿越NAT.
TURN应用模型通过分配TURN Server的地址和端口作为私网中VOIP终端对外的接受地址和端口,
即私网终端发出的报文都要经过TURN Server进行Relay转发,这种方式除了具有STUN方式的优点外,
还解决了STUN应用无法穿透对称NAT(Symmetric NAT)以及类似的Firewall设备的缺陷,


同时TURN支持基于TCP的应用,如H323协议。此外TURN Server控制分配地址和端口,
能分配RTP/RTCP地址对(RTCP端口号为RTP端口号加1)作为私网终端用户的接受地址,
避免了STUN方式中出口NAT对RTP/RTCP地址端口号的任意分配,
使得客户端无法收到对端发来的RTCP报文(对端发RTCP报文时,目的端口号缺省按RTP端口号加 1发送)。


TURN的局限性在于需要VOIP终端支持TURN Client,这一点同STUN一样对网络终端有要求。
此外,所有报文都必须经过TURN Server转发,增大了包的延迟和丢包的可能性。


5. ICE方式
应该说ICE是目前在NAT穿透中最常用的方式。
ICE(交互式连接建立)- Interactive Connectivity Establishment 是一种综合性的NAT穿越的技术。
交互式连接建立是由IETF的MMUSIC工作组开发出来的一种framework,
可整合各种NAT 穿透技术,如STUN、TURN(Traversal Using Relay NAT)、
RSIP(Realm Specific IP,特定域IP)等。
该framework可以让SIP的客户端利用各种NAT穿透方式打穿远程的防火墙。

2.6 协商媒体会话

现在两个浏览器如何进行彼此对话了,它们必须都同意他们将交互的媒体(如音频和视频)的类型和格式,
包括codec, 分辨率,码率等。
通常可以使用基于 offer/answer的模型来进行协商,
例如可以使用Session Description Protocol (SDP)
(http://en.wikipedia.org/wiki/Session_Description_Protocol).
它已经被JavaScript Session Establishment Protocol (JSEP)定义。
更多的信息可以访问:
http://tools.ietf.org/html/draft-ietf-rtcweb-jsep-00)
by the IETF

2.7 启动 RTCPeerConnection流

当前面的都完成后,最后浏览器就能启动彼此的流媒体交互,
两者可以直接通过它们的P2P连接,也可以是通过任何中继媒介网关。

在这个阶段,浏览器为了共享通信以控制WebRTC的通信,可以连续地使用同一个信令服务器方案。
它们也可以使用一个特殊WebRTC数据通道来直接通信做到这一点。

2.8 使用WebSockets

WebSocket API能让Web开发者很方便地在他们的Web应用开发出双向通信功能。
只需要很简单地使用:
  var connection = new WebSocket(url);
就能创建一个新的连接,
然后,构造函数就会创建你自己的函数来处理收到的消息和错误。
发送消息也很简单,使用:
 connection.send(message)
方法即可。

使用WebSocket的最大好处是它的消息是真正双向的,快速且轻量的。
这意味着WebSocket API服务器可以在任何想要的时候直接发送消息到你的浏览器,
并且你同样能即时收到它们。
它们没有延时或像在XHR轮循或长轮循模型那样的常量的网络阻塞,

WebSocket API服务器可以使用前面提到的唯一的房间或会话标识符,
来计算得到哪个WebSocket API客户端的消息应当被中转。
在这种方式中,单个WebSocket API服务器可以支持大量的客户端。
并且因为建立网络连接的发生次数非常少,因此消息本身是倾向于小的,因此要求的服务器资源也非常少。

在绝大多数的主流编程语言中有很多可用的WebSocket API库,
并且因为Node.js是基于JavsScript的,所以它变成了实现这种类型的功能非常流行的选择。
像socket.io(http://socket.io/)库都提供很多容易使用的示例程序。

2.9 其它信令选项

任何通过一种允许浏览器通过服务器来发送和接收消息的方法都可以用作WebRTC信令。

最简单的模式是使用XHR API来发送消息,并通过定期轮循服务来收集任何新的消息。
这种方式可以很容易实现,且不需要任何额外的工具。
然而,它有一些缺点。
它有一个基于每个轮循周期的必然延时,
而且它很浪费带宽,因为即时没有消息准备发送或接收,轮循也是周期重复的。
但是,如果你要想要一个好用的老式的方案,那它就是的。

有一种基于轮循的稍微精细点的方法,叫做长轮循。
在这种模型中,通过使用HTTP 1.1的keep-alive机制,
如果服务器没有任何消息,那么网络连接就一直保持。
当服务器有新的信息时,它就马上发送它到连接以完成请求。
在这种情况下,减少了轮循的网络开销。
相比于更现代的WebSocket模式,它还是过时和低效的方法。


Server-Send Events是另一个方式。
你可以使用:
  var source = new EventSource(url)
来建立到服务器的连接,构造函数然后添加对source对象的监听器,
用来处理由服务器发来的消息。
这种方式允许服务器直接发送消息,且你能即时收到。
但你使用的还是一个分立的通道,如XHR,来发送你的消息到服务器。,
这意味你被强制去管理和同步两个独立的管道。
这种组合模式提供了一个有用的方案,它被用在了一些WebRTC示例应用中,
但是它还不是真正的双向管道。


2.10 MediaStream API

MediaStream API用来访问本地输入设备,如摄像头和麦克风的媒体流。
getUserMedia()方法是初始化访问本地输入设备的主要方法。


每个MediaStream对象可以包含多个不同的MediaStreamTrack对象--它们每一个都表示一个不同的输入媒体,
如源自不同输入源的视频或音频。
每个MediaStreamTrack可以包含多个通道(如左右声道)。
通道是MediaStream API定义的最小单元。


MediaStream对象可以以两种主要方式输出。
首先,它们可以被用来渲染输出给 MediaElemet, 如 <video>或<audio>元素。
第二,它们可以被用来发送到一个RTCPeerConnection--它可以将媒体流发送到远端。


每个MediaStreamTrack可以使用一系列状态来表示,
由MediaSourceStates对象的states()方法的返回值描述。
每个MediaStreamTrack同样提供了一系列能力,可以通过capabilities()方法来访问。


在最上层,MediaStream对象可以触发一系列的事件,例如addtrack, removetrack,或ended.
而下层的MediaStreamTrack也可以触发一系列的事件,如started, mute, unmute, voerconstrainted 和 ended.


2.11 RTCPeerConnection API

RTCPeerConnection API是每个WebRTC间P2P连接的核心。
可以使用:
  var peerconnection = RTCPeerConnection(configuration);
来创新一个RTCPeerConnection对象,
configuration变量包含了至少一个主要的 iceServers的名字--它是一个URL对象数组,
包含了STUN,TURN服务器的信息,用来在候选查找。

peerconnection对象在随后的每个客户端,依赖于它是呼叫者还是被呼叫者,在使用时会稍微有些不同。

2.11.1 呼叫者流程
下面是peerconnection对象创建后,呼叫者的流程总结:
. 注册 onicecandidate 句柄
. 注册 onaddstream 句柄
. 注册 message 句柄
. 使用 getUserMedia 访问本地摄像头
. JSEP offer/answer 处理

1. 注册 onicecandidate 句柄
首先,需要注册一个onicecandidate句柄来发送任何ICE候选给其它端,
一旦收到后,就会使用其中一个信令通道。

2. 注册 onaddstream 句柄
然后,一旦收到了远端的视频流,就需要注册一个 onaddstream句柄来显示这个视频流。

3. 注册 message 句柄
你的信令通常也需要有一个句柄被注册,用来响应从其它端收到的消息。
如果消息中包含有 RTCIceCandidate对象,
那么应该使用addIceCandidate()方法将它添加到 peerconnection对象中。
如果消息中还包含有 RTCSessionDescription对象 ,
那么应该使用 setRemoteDescription()方法将它添加到 peerconnection对象中。

4. 使用 getUserMedia 访问本地摄像头
然后,你可以使用 getUserMedia()来设置你的本地媒体流和本地页面的显示,
同样的,也需要使用 addStream()方法将它添加到 peerconnection对象中。

5. JSEP offer/answer 处理
现在,你需要使用 createOffer()方法来准备好开始协商,
并注册一个回调句柄--它由RTCSessionDescription对象收到。
这个回调句柄应当使用 setLocalDescription()将RTCSessionDescription添加到你的peerconnection对象。
最后,你要通过信令通道将这个RTCSessionDescription发送到远端。

2.11.2 被呼叫者流程
The following is a summary of the callee's flow, which is very similar in a lot of
ways to the caller's flow, except that it responds to the offer with an answer:
下面是被呼叫都的流程总结,除了提供一个应答响应外,它和呼叫者的流程很相似:
.  注册 onicecandidate 句柄
.  注册 onaddstream    句柄
.  注册 消息句柄
.  使用 getUserMedia 访问本地摄像头
.  JSEP offer/answer 处理


1. 注册 onicecandidate 句柄
Just like the caller, you start by registering an onicecandidate handler that sends
any ICE candidates to the other peer as they are received, using one of the signaling
channels described previously.


2. 注册 onaddstream    句柄
Then, like the caller, you register an onaddstream handler that displays the video
stream once it is received from the remote peer.


3. 注册 消息句柄
Like the caller, your signaling channel should also have a handler registered
that responds to messages received from the other peer. If the message contains
an RTCIceCandidate object, it should add those to the peerconnection
object using the addIceCandidate() method. And if the message contains an
RTCSessionDescription object, it should add those to the peerconnection
object using the setRemoteDescription() method.


4. 使用 getUserMedia 访问本地摄像头
Then, like the caller, you can utilize getUserMedia() to set up your local media
stream and display that on your local page, and also add it to the peerconnection
object using the addStream() method.


5. JSEP offer/answer 处理
Here you differ from the caller and you play your part in the negotiation by
passing remoteDescription to the createAnswer() method and registering a
callback handler that receives an RTCSessionDescription object. This callback
handler should then add this RTCSessionDescription to your peerconnection
object using setLocalDescription(). And then finally, it should also send this
RTCSessionDescription to the remote peer through your signaling channel. It is
also important to note that this callee flow is all initiated after the offer is received
from the caller.

2.12 RTCPeerConnection在WebRTC架构中的位置

下面是整个WebRTC的架构图,
它显示了隐藏在RTCPeerConnection API下的复杂结构。

图1 WebRTC架构图 来自 www.WebRTC.org

2.13 RTCdataChannel API

在端之间使用WebRTC做媒体流发送的同时,
它还可以使用 DataChannelAPI传输二进制数据流。
这个API最准确的定义应该是 WebRTC DataChannel API,
它由:
  var datachannel = peerconnection.createDataChannel(label);
创建。
它很灵活,也很强大,和WebSocket API的send()方法相似。
展开阅读全文

没有更多推荐了,返回首页