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.