!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.