http://xiaol.me/2014/08/24/webrtc-stun-turn-signaling/
原文:WebRTC in the real world: STUN, TURN and signaling By Sam Dutton
WebRTC 實現了網頁點對點交流。
但是…
WebRTC 仍然需要服務器來:
- 交換客戶端元數據協調通訊,即信令(Signaling)。
- 應對NATs(Network Address Translators) 和防火牆。
本文將向你展示如何建立一個信令服務器,並使用STUN和TURN服務器來處理實際應用中出現的一些怪異的連接問題。也將解釋WebRTC應用是如何處理多方通訊並與類似VoIP、PSTN的服務互動的。
如果你沒有了解過WebRTC,我強烈建議你在看這篇文章之前先看看這篇文章 Getting Started With WebRTC
什么是信令?
信令即協調通訊的過程。WebRTC應用要發起一個對話,客戶端就需要交換如下信息:
- 用於打開和關閉通訊的會話信息;
- 錯誤信息;
- 媒體元數據如編解碼器及其設置,帶寬和媒體類型;
- 秘鑰數據,用於創建安全連接;
- 網絡數據,如外部能訪問的主機IP和端口。
這個信令過程需要客戶端之間能來回傳遞消息,但是WebRTC APIs並沒有提供這種機制的實現,你需要自己創建。下面將描述建立信令服務器的幾種方式。不管怎么樣,先來點上下文吧…
為什么WebRTC不提供信令實現?
為了避免冗余,以及做到與現有技術的最大兼容,信令方法和協議都不由WebRTC標准來指定。這些都由JSEP(JavaScript Session Establishment Protocol)來概述.
WebRTC呼叫建立背后的想法已經是完全指定和控制媒體鏈接,但是盡量托管和應用間的信令連接。
由是不同的應用可能會喜歡用不同的協議,比如已存在的SIP、Jungle信令協議,或者也許為了一些新奇的用例而做的特殊應用而自定義的協議。
這一節文字要傳達的關鍵信息點是多媒體會話的描述,這個描述指定了必要的傳輸和建立媒體鏈接所必要的媒體配置信息。
JSEP的架構也避免了讓瀏覽器去保存狀態,那就是,像一個信令狀態機一樣工作。這里也許會有一個問題,比如,當頁面被刷新時,信令數據會丟失。不過,也可以把這些信令狀態存在服務器。
JSEP需要offer和answer之間做出之前提到的媒體元數據的信息交換。offer和answer通過Session Description Protocol(SDP)格式來溝通,如下
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
v=0 o=- 7614219274584779017 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE audio video a=msid-semantic: WMS m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126 c=IN IP4 0.0.0.0 a=rtcp:1 IN IP4 0.0.0.0 a=ice-ufrag:W2TGCZw2NZHuwlnf a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=mid:audio a=rtcp-mux a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe a=rtpmap:111 opus/48000/2 … |
想知道SDP的格式的所有明確含義,可以看看這個IETF examples.
請記住WebRTC被設計使得offer或answer可以在被擰在一起之前通過編輯SDP文本來設置好本地或遠程描述。比如apprtc.appspot.com中的preferAudioCodec()
方法就被用於設置默認的編解碼器和比特率。SDP用JavaScript來操作是有點痛苦,所以現在有個討論是關於WebRTC的未來版本是否可以用JSON格式來替代,不過這里提到了一些堅持使用SDP的好處。
RTCPeerConnection + 信令: offer, answer and candidate
RTCPeerConnection接口被WebRTC應用用於創建各點之間的連接並交流視音頻信息。
要開始這個過程RTCPeerConnection需要先做兩個工作:
- 確定本地媒體情況,比如分辨率和編解碼器的能力。這些元數據會用在offeranswer機制中。
- 獲取可能的應用主機網絡地址,就死所謂的candidate。
當本地信息被確認后,就會通過信令系統與遠程終端進行交換。
聯想下alice is trying to call Eve這幅漫畫,發起/響應機制在其中完整的展現出來:
- Alice創建一個RTCPeerConnection對象。
- Alice通過RTCPeerConnection的createOffer()方法創建一個offer(SDP會話描述)。
- Alice通過這個offer調用setLocalDescription()。
- Alice將offer字符串化並通過信令服務器發給Eve。
- Eve通過調用setRemoteDescription()設置Alice的offer,來讓自己的RTCPeerConnection知道Alice的設置。
- Eve調用createAnswer()和成功回調函數來傳遞Eve的本地會話描述—answer。
- Eve通過setLocalDescription()來將她的本地描述設置到她的answer中。
- 然后Eve將她字符串化后的answer通過信令服務器發回給Alice。
- Alice通過setRemoteDescription()將Eve的anwser設置為遠程會話描述。
Alice和Eve還需要交換網絡信息。’finding candidates’就是通過ICE框架找到網絡鏈接和端口的過程。
- Alice 通過onicecandidate事件處理器來創建RTCPeerConnection對象。
- 這個事件處理器將在candidates可用時被調用。
- 在這個處理器中,Alice通過信令服務器將candidate數據字符串化后發送給Eve。
- 當Eve得到Alice的candidate信息,她將調用addIceCandidate()方法將這個candidate加入自己的遠程終端描述中。
JSEP支持ICE Candidate Trickling, 這個可以使呼叫者在初始化offer之后增量的提供candidates給被呼叫者,被呼叫者在這個呼叫中直接開始設置鏈接而不用等待收到其他candidates。
編寫WebRTC信令服務
下面是一個簡略的信令過程W3C代碼示例。這片代碼假設已經存在一些信令機制,如SignalingChannel. 下面討論信令的一些詳細細節。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
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); } |
要知道這片代碼中offer/answer和candidate交換過程是如何運作的,可以看看simpl.info/pc 中視頻聊天示例的控制台記錄。如果你需要跟多細節,可以下載完整的WebRTC信令轉儲,並通過Chrome的 chrome://webrtc-internals 或Opera的 opera://webrtc-internals 頁面來統計。
終端的發現
要說清楚’我怎么才能找到某人來聊天’挺復雜的。
對於電話來說,我們有電話號碼目錄。對於在線視頻聊天,我們需要身份認證以及在線狀態管理系統,即用戶初始化會話。WebRTC應用需要一種方式來讓客戶端來互相標識他們是像創建一個聊天室還是加入一個聊天。
WebRTC沒有提供終端目錄機制,所以我們不會進入這一項。這個過程可以簡單的通過郵件或信息分享一個URL,比如 talky.io、tawk.com 和 browsermeeting.com這些視頻聊天應用中,你邀請別人加入是通過跟他們分享你的自有鏈接。開發者Chris Ball創建了一個有趣的實驗serverless-webrtc讓WebRTC的參與者通過IM,email或者信鴿來交換元數據。
要如何建立一個信令服務器?
重申一下,信令協議及機制並不由WebRTC標准定義。不管你選擇什么,你都需要一個中介服務器來交換客戶端之間的信令信息和應用數據。很可惜,網頁應用並不能簡單的直接沖着英特網說’把我和我的朋友連起來!’.
還好信令信息很小,並且大多數只在一個呼叫的開始才需要交換.在對apprtc.appspot.com和samdutton-nodertc.jit.su的測試中我們發現,一個視頻聊天會話中,信令服務器總共處理了30-45條消息,所有消息的總大小才10kb左右。
並且對帶寬的要求也較低,WebRTC信令服務器並不消耗太多cpu或內存,因為它們只需要做消息中轉,並保存少量的會話狀態數據(例如,有哪些客戶已經連接了)。
Tip!
信令機制可以用來交換會話元數據,也可以用來做應用數據通訊。它就是一個消息服務器。
從服務器推送消息到客戶端
信令的消息服務需要是雙向的:客戶端發到服務器且服務器發到客戶端。雙向通訊違反了HTTP協議的客戶/服務,請求/響應模型。不過一些hack,比如為了將數據從服務器推送到網頁的長輪詢)已經出現很多年了。
最近,EventSource API已被廣泛的應用了,他使得服務器通過HTTP發送數據到瀏覽器成為可能。這里有個簡單的demo。EventSource被設計成單向傳遞消息,但是它可以和XHR結合構建成交換信令消息的服務器:一個從呼叫者開始傳遞消息,用XHR請求傳輸,通過EventSource推送到被呼叫者那去。
WebSocket是一個更自然的解決方案,被設計成全雙工的客戶端/服務器通訊(消息可以同時雙向傳輸)。一個將信令服務器用純WebSocket或服務器發送事件(EventSource)的型式構建的好處是后台接口可以由各種語言的通用框架公共托管包來實現,比如PHP,Python和Ruby。
大概四分之三的瀏覽器都支持WebSocket了,更重要的是,所有支持WebRTC的瀏覽器都支持WebSocket,不管是桌面端還是移動端。所有連接都需要使用TLS,去保證不被截獲到未加密的信息,並且減少proxy traveral引起的問題。(需要更多WebSocket和proxy traversal相關的信息,可以看看Ilya Grigorik的High Performance Browser Networking一書的WebRTC章節。Peter Lubber的WebSocket Cheat Sheet有更多關於WebSocket客戶端和服務器端的信息)。
apprtc.appspot.comWebRTC視頻聊天應用的信令是通過Google App Engine Channel API完成的,這個API用到了Comet)技術(長輪詢)去實現信令推送信息(這里有一個App Engine為支持WebSocket存在很久的bug,快去關注這個bug,給它投票別讓它沉了!)。這里有一份這個應用的詳細代碼。
WebRTC客戶端通過ajax輪詢獲取服務器信息處理信令也是可行的,但是這導致太多冗余的網絡請求,尤其對於移動端客戶來說更是一個問題。甚至在一個會話建立之后,終端仍需要輪詢信令信息去查詢是否會話有變化或者會話是否被對方終止了。這個示例使用了該方法,但做了一些輪詢頻率的優化。
擴展信令服務器規模
雖然信令服務器對於每個客戶來說消耗的帶寬和CPU都較少,但是應用流行起來的話依然要處理不同地域的大量的數據,應對高並發。通信量較高的WebRTC應用需要能夠應對高負載。
這里我們不會討論細節,但仍有如下一些為高容量,高性能信息可以注意的點。
- eXtensible Messaging and Presence Protocol (XMPP), 最初被稱為Jabber: 一個為即時信息開發的協議,可應用於信令服務,這種XMPP服務器的實現包括ejabberd和Openfire.JavaScript客戶端如Strophe.js使用BOSH模擬雙向流,但是因為各種原因,BOSH的效率比不上WebSocket,而且也不易擴展。(話說Jingle)是一個使XMPP支持音頻和視頻的擴展;WebRTC項目使用的網絡傳輸組件libjingle庫就是Jingle的C++實現)
- 開源庫如ZeroMQ(TokBox用在他們的Rumour服務器上)和OpenMQ。NullMQ在Web平台應用了ZeroMQ的概念,基於WebSocket使用STOMP。
- 使用WebSocket的商業雲消息平台(雖然他們不使用長輪詢)如Pusher, Kaazing和PubNub。(PubNub還有WebRTC的接口)
(開發者Phil Leggetter的Real-Time Web Technologies Guide提供了一個關於消息服務和代碼庫的總結性清單。)
在Node上用Socket.io建立信令服務器
以下的簡單網頁應用代碼使用到了基於Node上的Socket.io而建立的信令服務器。Socket.io的設計使建立信息交換服務器變得簡單,而且它尤其適用於WebRTC信令服務器,因為它內置了’房間’的概念。這個例子不是為產品級別的信令服務器設計的,但是它面向相對較小的用戶群工作得很好。
Socket.io除了用WebSocket,還適配以下備用技術:Adobe Flash Socket, AJAX long polling, AJAX multipart streaming, Forever Iframe and JSONP polling. 它有多種后台實現,但是它的Node版本應該是最著名的,我們下面的例子就用的這個版本。
例子中沒有WebRTC,這里只是展示網頁應用信令該如何設計。查看控制台可以看到客戶是如何加入一個房間且交換信息的。我們的WebRTC codelab有如何將這個例子集成進完整的WebRTC視頻聊天應用的步驟。你可以在codelab repo第五步下載這些代碼,也可以在samdutton-nodertc.jit.su在線試試效果。
index.html的代碼如下:
1
2 3 4 5 6 7 8 9 10 |
<!DOCTYPE html> <html> <head> <title>WebRTC client</title> </head> <body> <script src='/socket.io/socket.io.js'></script> <script src='js/main.js'></script> </body> </html> |
html中引用的的javascript文件main.js代碼如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var isInitiator; room = prompt('Enter room name:'); var socket = io.connect(); if (room !== '') { console.log('Joining room ' + room); socket.emit('create or join', room); } socket.on('full', function (room){ console.log('Room ' + room + ' is full'); }); socket.on('empty', function (room){ isInitiator = true; console.log('Room ' + room + ' is empty'); }); socket.on('join', function (room){ console.log('Making request to join room ' + room); console.log('You are the initiator!'); }); socket.on('log', function (array){ console.log.apply(console, array); }); |
完整的服務端應用代碼:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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); }); }); |
(你並不需要知道這代碼中的node-static是啥,它只是讓服務器代碼簡單點。)
要在本地啟動這個應用,你需要安裝Node, socket.io和node-static。Node可以直接在官網下載(安裝過程很簡單)。要安裝socket.io和node-static,在你的應用目錄終端運行Node包管理器(NPM)就行了.
1
2 |
npm install socket.io npm install node-static |
要運行應用,只需要在你應用目錄里終端運行如下命令:
1
|
node server.js |
在你的瀏覽器中打開localhost:2013
。在新的標簽頁或窗口將localhost:2013
再打開一次。看看發生了什么,檢查下Chrome或Opera的控制台,你可以用通過快捷鍵Command-Option-J
或Ctrl-Shift-J
來打開開發者工具DevTool。
不管你選擇什么來實現你的信令,你的后台和客戶端都至少至少需要提供一個和這個例子類似的服務。
在信令服務器中使用RTCDataChannel
信令服務器需要初始化一個WebRTC會話。
然而,當兩個終端間的連接建立后,RTCDataChannel理論上可以當作信令通道。這個可以減少信令的延遲並且減少信令服務器帶寬和cpu的消耗,因為這樣的信息是直接交流的。這里我們沒有demo,不過大家仍需留意。
信令陷阱?
- 在
setLocalDescription()
方法被調用前RTCPeerConnection都不會開始收集candidates,這是JSEP IRTF draft中要求的。 - 利用Trickle ICE(見前文):收到candidates信息立刻調用
addIceCandidate()
方法。
現成的信令服務器
如果你不想你自己來做信令服務器,這里有提供一些WebRTC信令服務器,用的也是之前提到的Socket.io,並都集成了WebRTC客戶端JavaScript代碼庫。
- webRTC.io:WebRTC的第一個抽象代碼庫
- easyRTC:全棧WebRTC包
- Signalmaster:為SimpleWebRTCJavaScript庫創建的信令服務器。
…如果你壓根任何代碼都不想寫,這里也有一些完全商業化的WebRTC平台如vLine,OpenTok,Asterisk.
需要指出來,Ericsson在WebRTC早期就已經用PHP在Apache上搭了個信令服務器。但是這個現在多少已經廢棄了,不過如果你在考慮做類似的事的話,這代碼還是值得一看的。
信令安全
Security is the art of makeing nothing happen.
—Salman Rushdie
加密在WebRTC組件中是強制的。
然而,信令機制並不由WebRTC標准所定義,所以讓信令更安全就是你自己的事了。如果攻擊者試圖劫持信令, HTTPS和WSS(i.e TLS),可以保證他們不會攔截到未加密的信息。你也要注意不要在其他用同一個服務器的用戶能訪問到的地方廣播信令信息。
要保護WebRTC應用,在信令中使用TLS是絕對必要的。
信令之外:使用ICE應付NAT和防火牆
對於信令元數據,WebRTC應用使用了中介服務器,但是對於會話建立后的真正媒體數據流,RTCPeerConnection試圖讓客戶終端直連:點對點連接。
簡單的情況下,每個WebRTC終端都有一個唯一的地址,可以使得各終端都能互相直接通訊。
{}
但是大多數設備都處於一層或多層NAT(網絡地址轉換器)之后,還有殺毒軟件的阻擋了一些端口或協議,又或者使用了代理或者防火牆。防火牆和NAT事實上可能在同一設備上,比如家庭無線路由器。
WebRTC應用可以使用ICE框架來克服實際應用中復雜的網絡問題。要使用ICE的話,你的應用必須如下所述的在RTCPeerConnection中傳遞ICE服務器的URL。
ICE試圖找到連接端點的最佳路徑。它並行的查找所有可能性,然后選擇最有效率的一項。ICE首先用從設備操作系統和網卡上獲取的主機地址來嘗試連接,如果失敗了(比如設備處於NAT之后),ICE會使用從STUN 服務器獲取到的外部地址,如果仍然失敗,則交由TURN中繼服務器來連接。
換句話說:
- STUN服務器用於獲取設備的外部網絡地址
- TURN服務器是在點對點失敗后用於通信中繼。
每一個TURN服務器都支持STUN,因為TURN就是在STUN服務器中內建了一個中繼功能。ICE也可以應付NAT復雜的設定:實際上,NAR’打洞’會有不止一個公共 IP : port 地址。STUN或TURN服務器的URL由WebRTC中RTCPeerConnection的第一個參數iceServers配置對象可選指定。apprtc.appspot.com中的值是這樣的:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{
'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
NAT在本地私有網絡中為設備提供了一個IP地址,但是這個地址並不能被外部識別。沒有一個公共地址的話,WebRTC終端是沒有辦法通信的。要解決這個問題,WebRTC使用了STUN。
STUN服務器處於公網中並有個簡單任務:檢查請求(來自運行於NAT之后的應用)的IP:port 地址,並且將這個地址響應回去。換句話說,NAT后的應用使用STUN服務器來找到他的IP:port 公網地址。這個過程使得WebRTC終端可以找到自己公共訪問方法,並通過信令機制將之發送給其他終端,就可以創建一個直連鏈接。(在實踐中,不同的NAT工作方式不同,並有可能有多層NAT,但是原理是一樣的。)
STUN服務器並沒有做太多東西,也不用記住很多東西,所以一個相對低規格的的STUN服務器可以處理大量的請求。
根據webrtcstats.com的調查,大部分(86%)WebRTC請求都可以通過STUN成功的創建連接,雖然對處於防火牆或者配置復雜的NAT之后的終端要低一些。
TURN
RTCPeerConnection嘗試用UDP協議建立終端間的直連。如果失敗了,就嘗試TCP協議,還是失敗的話,TURN 服務器就會用於做終端間的數據中繼。
重申一下:TURN用於中繼視頻音頻數據流,而不是信令數據!
TURN服務器有公共地址,所以他可以被終端聯系到,哪怕終端處於防火牆或者代理之后。TURN服務器有一個概念上簡單的工作—做數據流中繼—但是,不像STUN服務器,它天生需要消耗大量帶寬,也就是說,TURN服務器需要很強大。
這幅圖展現了TURN的運作,純STUN不能成功的話,各終端將使用TURN服務器。
部署STUN和TURN服務器
Google運行了一個公用的STUN服務器用作測試,stun.l.google.com:19302
,apprtc.appspot.com用到了它。我們建議使用rfc5766-turn-server當作產品用途的STUN/TURN服務,STUN/TURN服務器的源代碼可以在code.google.com/p/rfc5766-turn-server 找到,這里也提供了一些服務器安裝的相關信息鏈接。Amazon Web Services(AWS)也提供了WebRTC的虛擬機鏡像。
另一個備選TURN服務器是restund,有源代碼,也可以裝到在AWS上。下面是介紹如何將restund裝到Google Compute Engine上。
- 防火牆開放tcp=443,udp/tcp=3478
- 創建4個實例(?),各自的公用IP,使用標准Ubuntu 12.06鏡像
- 配置本地防火牆(允許所有訪問源)
- 安裝工具:
sudo apt-get install make
sudo apt-get install gcc
- 從creytiv.com/re.html安裝libre
- 從creytiv.com/restund.html獲取restund並解包
- wget hancke.name/restund-auth.patch 並應用
patch -p1 < restund-auth.patch
- 對libre和restund運行 make,
sudo make install
- 按你自己的需求配置restund.conf(替換IP地址,確保正確的共享密鑰)並復制到
/etc
目錄 - 復制restund/etc/restund到/etc/init.d/
- 配置restund:
設置LD_LIBRARY_PATH
復制restund.conf到/etc/restund.conf
設置restund.conf使用之前配的IP地址 - 運行restund
- 在遠程機器運行stund client命令做測試:
./client IP:port
點對點之外:多方WebRTC通訊
你也許會對Justin Uberti為REST API for access to TURN Services提出的IETF標准感興趣。
很容易想到一個超越簡單的點對點媒體流用例:比如,同事間的視頻會議,或者一個有數百(萬)用戶的公共演講。
WebRTC應用可以使用多RTCPeerConnection,讓各終端之間以網狀配置連接。這就是如talky.io這類應用所使用的方法,並且在少量終端的情況下運行的非常良好。不過,CPU和帶寬都消耗非常多,尤其是在移動終端上。
此外,WebRTC應用可以按星狀拓撲結構來選擇一個終端分發數據流。在服務器運行一個WebRTC終端來作為重新分配機制也是可行的(webrtc.org提供了一個簡單例子)。
從Chrome 31和Opera 18開始,RTCPeerConnection的MediaStream可以當作另一個RTCPeerConnection的輸入:這里有個簡單演示simpl.info/rtcpeerconnection/multi, 這使得應用結構更靈活,因為它使網絡應用通過選擇其他終端的連接來處理路由成為可能。
Multipoint Control Unit
對於大量終端的更好選擇是使用Multipoint Control Unit(MCU).這是一個服務器,像大量參與者之間的橋梁一樣用於分發媒體信息。MCU可以在一個視頻會議中使用多種分辨率,編解碼器和幀率,處理轉換編碼,選擇數據流徑,調制或錄制視頻音頻。對於多方通話,有一堆問題需要注意: 特別是,如何顯示多視頻輸入和混調多源音頻。雲平台如vLine有嘗試優化流量路徑。
可以考慮買一個MCU的硬件,或者自己做一個。

有不少能用的開源MCU軟件供選擇。比如,Licode(之前叫Lynckia)就為WebRTC做了一個開源MCU,OpenTok也有一個開源產品Mantis。
瀏覽器之外: VoIP, 電話和短信
WebRTC的標准性質使得瀏覽器中運行的WebRTC應用可以和運行其他通信平台的設備或者平台建立通訊,比如電話或者視頻會議系統。
SIP是VoIP和視頻會議系統的信令協議。要使WebRTC網頁應用能和其他如視頻會議系統的SIP客戶端通訊,WebRTC需要一個代理服務器做中介信令。信令需要流過網關,但是一旦通信已經建立起來,SRTP(視頻和音頻)就可以點對點傳輸。
PSTN(Public Switched Telephone Network),公用電話交換網絡,是所有普通模擬電話的閉路交換網絡。要用WebRTC網頁應用打電話,流量必須經過PSTN網關。此外,WebRTC網頁應用需要用中介XMPP服務器來與Jingle)終端如即時通信客戶端通訊。
Jingle由Google開發來作為XMPP擴展用於支持視頻和音頻信息:現在WebRTC的實現基於libjingleC++庫,這個Jingle的實現剛開始是為Google Talk開發的。
一些應用,代碼庫和平台利用WebRTC的能力來於外界通訊,如:
- sipML5:開源JavaScript SIP客戶端
- jsSIP:JavaScript SIP代碼庫
- Phono:開源JavaScript電話接口,作為插件開發
- Zingaya:嵌入式電話組件
- Twilio:音頻消息應用
- Uberconference:會議系統
sipML5的開發們也開發了webrtc2sip網關。Tethr and Tropo在筆記本上演示過一個救災通訊框架, 使用OpenBTS cell讓電腦能通過WebRTC與一個特別的電話通訊。無需電信就能打電話啦!
更多
WebRTC codelab:一步一步介紹如何建立一個視頻文字聊天應用,使用了在Node中運行的Socket.io信令服務器。
2013 Google I/O 大會上由WebRTC技術組長Justin Uberti做的WebRTC報告。
Chris Wilson在SFHTML5上的報告:Introduction to WebRTC Apps
WebRTC Book提供了很多數據和信令路徑的詳細信息,包括了許多詳細的網絡拓撲圖。
WebRTC and Signaling: What Two Years Has Taught Us:TokBox的一篇博文告訴我們為什么要把信令從WebRTC細則中單獨拎出來。
Ben Strong的報告A Practical Guide to Building WebRTC Apps提供了很多WebRTC拓撲和基礎。
Ilya Grigorik的High Performance Browser Networking一書中的WebRTC章節深入描述了WebRTC結構,用例和性能。