摘要: 深入JS系列18。
Fundebug經授權轉載,版權歸原作者所有。
這是專門探索 JavaScript 及其所構建的元件的系列文章的第 18 篇。
如果你錯過了前面的章節,可以在這裡找到它們:
- JavaScript 是如何工作的:引擎,執行時和呼叫堆疊的概述!
- JavaScript 是如何工作的:深入V8引擎&編寫優化程式碼的5個技巧!
- JavaScript 是如何工作的:記憶體管理+如何處理4個常見的記憶體洩漏!
- JavaScript 是如何工作的:事件迴圈和非同步程式設計的崛起+ 5種使用 async/await 更好地編碼方式!
- JavaScript 是如何工作的:深入探索 websocket 和HTTP/2與SSE +如何選擇正確的路徑!
- JavaScript 是如何工作的:與 WebAssembly比較 及其使用場景!
- JavaScript 是如何工作的:Web Workers的構建塊+ 5個使用他們的場景!
- JavaScript 是如何工作的:Service Worker 的生命週期及使用場景!
- JavaScript 是如何工作的:Web 推送通知的機制!
- JavaScript是如何工作的:使用 MutationObserver 跟蹤 DOM 的變化!
- JavaScript是如何工作的:渲染引擎和優化其效能的技巧!
- JavaScript是如何工作的:深入網路層 + 如何優化效能和安全!
- JavaScript是如何工作的:CSS 和 JS 動畫底層原理及如何優化它們的效能!
- JavaScript的如何工作的:解析、抽象語法樹(AST)+ 提升編譯速度5個技巧!
- JavaScript是如何工作的:深入類和繼承內部原理+Babel和 TypeScript 之間轉換!
- JavaScript是如何工作的:儲存引擎+如何選擇合適的儲存API!
- JavaScript是如何工作的: Shadow DOM 的內部結構+如何編寫獨立的元件!
概述
WebRTC,名稱源自網頁即時通訊(英語:Web Real-Time Communication)的縮寫,是一個支援網頁瀏覽器進行實時語音對話或視訊對話的API。
在此之前,P2P技術(如桌面聊天應用程式)可以做一些網路做不到的事情,WebRTC 填補了 Web 這一關鍵空白點。
WebRTC 是一項實時通訊技術,它允許瀏覽器或者 app 之間可以不借助中間媒介的情況下,建立瀏覽器之間點對點的連線,實現視訊流和音訊流或者其他任意資料的傳輸。本文中討論這一點,還支討論以下主題,以便讓你全面瞭解 WebRTC 的內部結構:
- 點對點通訊 (Peer-To-Peer communication)
- 防火牆和NAT穿透 (Firewalls and NAT Traversal)
- 信令、會話和協議 (Signaling, Sessions, and Protocols)
- WebRTC APIs
點對點通訊
為了通過 Web 瀏覽器與另一個對等點進行通訊,每個 Web 瀏覽器必須經過以下步驟:
- 是否同意進行通訊
- 彼此知道對方的地址
- 繞過安全和防火牆保護
- 實時傳輸所有多媒體通訊
基於瀏覽器的點對點通訊相關的最大挑戰之一是知道如何定位和建立與另一個 Web 瀏覽器的網路套接字連線,以便雙向傳輸資料。
當 Web 應用程式需要一些資料或資源時,它從某個伺服器獲取資料或資源,僅此而已。但是,如果想建立點對點視訊聊天,通過直接連線到其他人的瀏覽器——你不知道對方地址,因為另一個瀏覽器不是已知的 Web伺服器。因此,為了建立點對點連線,還需要做更多的工作。
防火牆和 NAT 穿透 (Firewalls and NAT Traversal)
NAT(Network Address Translation,網路地址轉換)是1994年提出的。當在專用網內部的一些主機本來已經分配到了本地 IP 地址 (即僅在本專用網內使用的專用地址),但現在又想和因特網上的主機通訊(並不需要加密)時,可使用 NAT 方法。
NAT(Network Address Translation,網路地址轉換)簡單來說就是為了解決 IPV4 下的IP地址匱乏而出現的一種技術。 舉例,就是通常我們處在一個路由器之下,而路由器分配給我們的地址通常為191.168.0.21 、191.168.0.22如果有n個裝置,可能分配到192.168.0.n,而這個IP地址顯然只是一個內網的IP地址,這樣一個路由器的公網地址對應了 n 個內網的地址,通過這種使用少量的公有 IP 地址代表較多的私有 IP 地址的方式,將有助於減緩可用的IP地址空間的枯竭。
NAT技術會保護內網地址的安全性,所以這就會引發個問題,就是當我採用P2P之中連線方式的時候,NAT會阻止外網地址的訪問,這時我們就得采用 NAT 穿透了。
這就是 NAT (STUN) 的會話遍歷實用程式和圍繞 NAT (TURN)伺服器使用中繼進行遍歷的原因。為了讓WebRTC 技術能夠正常工作,首先會向 STUN 伺服器請求你的公開IP地址。可以把它想象成你的計算機向遠端伺服器進行查詢,該伺服器詢問它接收查詢的IP地址,然後遠端伺服器用它看到的 IP 地址進行響應。
假設這個過程有效,並且你接收到你面向公眾的 IP 地址和埠,那麼你就能夠告訴其他對等方如何直接連線到你。這些對等點還可以使用 STUN 或 TURN 伺服器做同樣的事情,並可以告訴你用什麼地址與它們聯絡。
STUN(Simple Traversal of UDP over NATs,NAT 的UDP簡單穿越)是一種網路協議,它允許位於NAT(或多重NAT)後的客戶端找出自己的公網地址,查出自己位於哪種型別的NAT之後以及NAT為某一個本地埠所繫結的Internet端埠。這些資訊被用來在兩個同時處於NAT 路由器之後的主機之間建立UDP通訊。該協議由RFC 3489定義。目前RFC 3489協議已被RFC 5389協議所取代,新的協議中,將STUN定義為一個協助穿越NAT的工具,並不獨立提供穿越的解決方案。它還有升級版本RFC 7350,目前正在完善中。
TURN的全稱為Traversal Using Relay NAT,即通過Relay方式穿透 NAT,TURN 應用模型通過分配TURNServer的地址和埠作為客戶端對外的接受地址和埠,即私網使用者發出的報文都要經過TURNServer進行Relay轉發,這種方式應用模型除了具有STUN方式的優點外,還解決了STUN應用無法穿透對稱NAT(SymmetricNAT)以及類似的Firewall裝置的缺陷
信令、會話和協議
上述網路資訊發現過程是較大的信令主題的一部分,其基於 WebRTC 情況下的 JavaScript 會話建立協議(JSEP)標準。 信令涉及網路發現和 NAT 穿透,會話建立和管理,通訊安全性,媒體能力後設資料和協調以及錯誤處理。
為了使連線起作用,對等方必須獲取後設資料的本地媒體條件(例如,解析度和編解碼器功能),並收集應用程式主機的可能網路地址,用於來回傳遞這些關鍵資訊的信令機制並未內建到 WebRTC API 中。
信令不是由 WebRTC 標準指定的,也不是由其 Api 實現的,這樣可以保持技術和協議的靈活性。信令和處理它的伺服器由 WebRTC 應用程式開發人員處理。
假設 WebRTC 瀏覽器的應用程式能夠使用 STUN 確定其面向公共的IP地址,下一步是實際地與對等方協商並建立網路會話連線。
初始會話協商和建立使用專門用於多媒體通訊的信令/通訊協議進行,該協議還負責管理會話的管理和終止規則。
其中一個協議是會話啟動協議(稱為SIP)。請注意,由於WebRTC信令的靈活性,SIP不是唯一可以使用的信令協議。所選的信令協議還必須與一個稱為會話描述協議(SDP)的應用層協議一起工作,該協議在WebRTC的情況下使用。所有特定於多媒體的後設資料都使用SDP協議傳遞。
嘗試與另一個對等體通訊的任何對等體(即,WebRTC-利用應用程式)生成一組互動式連線建立協議(ICE)候選者。 候選者代表要使用的IP地址,埠和傳輸協議的給定組合。 請注意,單臺計算機可能具有多個網路介面(無線,有線等),因此可以為每個介面分配多個IP地址。
這是一個來自MDN的圖表,描述了這種交換。
建立連線
每個對等點首先建立它所描述的面向公共的IP地址。然後動態建立信令資料“通道”來檢測對等點,並支援對等協商和會話建立。
外部世界不知道或無法訪問這些“通道”,因此需要一個惟一的識別符號來訪問它們。
請注意,由 於WebRTC 的靈活性,以及該標準沒有指定信令流程這一事實,考慮到所使用的技術,“通道”的概念和使用可能略有不同,事實上,有些協議不需要“通道”機制進行通訊。
這裡假設在本文的實現中使用了“通道”。
一旦兩個或更多個對等體連線到相同的“通道”,則對等點能夠通訊並協商會話資訊,此過程有點類似於釋出/訂閱模式。 基本上,發起對等體使用諸如會話發起協議 SIP 和 SDP 之類的信令協議傳送“offer(請求)”,發起者等待從連線到給定“通道”的任何接收器接收“answer(應答)”。
一旦收到答覆,就會發生以下過程,確定並協商每個對等點收集的最佳互動連線建立協議(ICE)候選者。 一旦選擇了最佳 ICE 候選者,基本上所有所需的後設資料,網路路由(IP地址和埠)以及用於為每個對等體通訊的媒體資訊達成一致。 然後,完全建立並啟用對等點之間的網路套接字會話。 接下來,由每個對等體建立本地資料流和資料通道端點,並且最終使用所採用的任何雙向通訊技術以雙向方式傳輸多媒體資料。
如果商定最佳 ICE 候選方案的過程失敗(有時確實由於使用了防火牆和 NAT 技術而發生這種情況),那麼可以使用 TURN 伺服器作為中繼。這個過程基本上使用一個充當中介的伺服器,它在對等點之間中繼任何傳輸的資料。請注意,這不是真正的對等通訊,在這種通訊中,對等點直接雙向地向彼此傳輸資料。
當使用 TURN 回退進行通訊時,每個對等方不再需要知道如何相互聯絡和傳輸資料。 相反,它們需要知道公共 TURN 伺服器在通訊會話期間傳送和接收實時多媒體資料。
重要的是要明白,這絕對是一個失敗的安全措施和最後的手段。TURN 伺服器需要非常健壯,具有廣泛的頻寬和處理能力,並處理潛在的大量資料。因此,使用 TURN 伺服器顯然會帶來額外的成本和複雜性。
SIP(Session Initiation Protocol,會話初始協議)是由IETF(Internet Engineering Task Force,因特網工程任務組)制定的多媒體通訊協議。它是一個基於文字的應用層控制協議,用於建立、修改和釋放一個或多個參與者的會話。廣泛應用於CS(Circuit Switched,電路交換)、NGN(Next Generation Network,下一代網路)以及IMS(IP Multimedia Subsystem,IP多媒體子系統)的網路中,可以支援並應用於語音、視訊、資料等多媒體業務,同時也可以應用於Presence(呈現)、Instant Message(即時訊息)等特色業務。可以說,有IP網路的地方就有SIP協議的存在。
SDP 完全是一種會話描述格式(對應的RFC2327) ― 它不屬於傳輸協議 ― 它只使用不同的適當的傳輸協議,包括會話通知協議(SAP)、會話初始協議(SIP)、實時流協議(RTSP)、MIME 擴充套件協議的電子郵件以及超文字傳輸協議(HTTP)。SDP協議是也是基於文字的協議,這樣就能保證協議的可擴充套件性比較強,這樣就使其具有廣泛的應用範圍。SDP 不支援會話內容或媒體編碼的協商,所以在流媒體中只用來描述媒體資訊。媒體協商這一塊要用RTSP來實現.
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
WebRTC APIs
- MediaStream — MediaStream用來表示一個媒體資料流,允許你訪問輸入裝置,如麥克風和 Web攝像機,該 API 允許從其中任意一個獲取媒體流。
- RTCPeerConnection — RTCPeerConnection 物件允許使用者在兩個瀏覽器之間直接通訊 ,你可以通過網路將捕獲的音訊和視訊流實時傳送到另一個 WebRTC 端點。使用這些 Api,你可以在本地機器和遠端對等點之間建立連線。它提供了連線到遠端對等點、維護和監視連線以及在不再需要連線時關閉連線的方法。
- RTCDataChannel — 表示一個在兩個節點之間的雙向的資料通道,每個資料通道都與RTCPeerConnection 相關聯。
MediaStream (別名getUserMedia)
MediaStream API 代表媒體流的同步。比如,從攝像頭和麥克風獲取的媒體流具有同步視訊和音訊軌道。
MediaDevices.getUserMedia()
會提示使用者給予使用媒體輸入的許可,媒體輸入會產生一個MediaStream,裡面包含了請求的媒體型別的軌道。此流可以包含一個視訊軌道(來自硬體或者虛擬視訊源,比如相機、視訊採集裝置和螢幕共享服務等等)、一個音訊軌道(同樣來自硬體或虛擬音訊源,比如麥克風、A/D轉換器等等),也可能是其它軌道型別。
它返回一個 Promise 物件,成功後會 resolve 回撥一個 MediaStream 物件。若使用者拒絕了使用許可權,或者需要的媒體源不可用,promise 會 reject 回撥一個 PermissionDeniedError 或者 NotFoundError 。
可以通過 navigator
物件訪問 MediaDevice 單例,如下所示:
通常你可以使用 navigator.mediaDevices 來獲取 MediaDevices ,例如:
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* 使用這個stream stream */
})
.catch(function(err) {
/* 處理error */
});
複製程式碼
請注意,constraints
引數是一個包含了video 和 audio兩個成員的MediaStreamConstraints 物件,用於說明請求的媒體型別。必須至少一個型別或者兩個同時可以被指定。如果瀏覽器無法找到指定的媒體型別或者無法滿足相對應的引數要求,那麼返回的Promise物件就會處於rejected[失敗]狀態,NotFoundError作為rejected[失敗]回撥的引數。
從版本25開始,基於 Chromium 的瀏覽器允許將來自 getUserMedia()
的音訊資料傳遞給音訊或視訊元素(但請注意,預設情況下,媒體元素將被靜音)。
getUserMedia
還可以用作 Web 音訊 API 的輸入節點:
function gotStream(stream) {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
// Create an AudioNode from the stream
var mediaStreamSource = audioContext.createMediaStreamSource(stream);
// Connect it to destination to hear yourself
// or any other node for processing!
mediaStreamSource.connect(audioContext.destination);
}
navigator.getUserMedia({audio:true}, gotStream);
複製程式碼
約束
getUserMedia()
是一個可能涉及重大隱私問題的 API,規範將其用於使用者通知和許可權管理的非常特定的需求。getUserMedia()
在開啟任何媒體收集輸入(如網路攝像頭或麥克風)之前,必須始終獲得使用者許可。瀏覽器可能提供每個域一次的許可權特性,但它們必須至少在第一次請求,如果使用者選擇這樣做,則必須特別授予正在進行的許可權。
同樣重要的是關於通知的規則。瀏覽器需要顯示一個指示器,該指示器顯示正在使用的攝像機或麥克風,超出可能存在的任何硬體指示器。它們還必須顯示一個指示符,表明已授予使用裝置進行輸入的許可權,即使該裝置目前沒有進行主動記錄
RTCPeerConnection
RTCPeerConnection 它代表了本地端機器與遠端機器的一條連線。該介面提供了建立,保持,監控,關閉連線的方法的實現。的作用是在瀏覽器之間建立資料的“點對點”(peer to peer)通訊.
下面是 WebRTC 架構圖,展示了 RTCPeerConnection 的作用:
從 JavaScript 的角度來看,從這個圖中要理解的主要事情是 RTCPeerConnection
為 Web 開發人員提供了一個抽象,從複雜的內部結構中抽象出來。使用WebRTC的編解碼器和協議做了大量的工作,方便了開發者,使實時通訊成為可能,甚至在不可靠的網路:
- 丟包隱藏
- 回聲抵消
- 頻寬自適應
- 動態抖動緩衝
- 自動增益控制
- 噪聲抑制與抑制
- 影象清洗
RTCDataChannel
除了視訊和音訊,webRTC 還可以傳輸其他資料,RTCDataChannel
API支援對等交換任意資料。
應用場景:
- 遊戲
- 遠端桌面應用程式
- 實時文字聊天
- Web檔案傳輸
API充分利用了 RTCPeerConnection
強大和靈活的點對點通訊
- 利用
RTCPeerConnection
會話。
* 多通道同步通道。
- 可靠和不可靠的傳遞語義(delivery semantics)。
- 內建安全(DTLS)和阻塞控制。
* 能夠使用或不使用音訊或視訊。
語法類似於已知的 WebSocket,使用 send()
方法和 message
事件:
var peerConnection = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]}
);
peerConnection.ondatachannel = function(event) {
receiveChannel = event.channel;
receiveChannel.onmessage = function(event){
document.querySelector("#receiver").innerHTML = event.data;
};
};
sendChannel = peerConnection.createDataChannel("sendDataChannel", {reliable: false});
document.querySelector("button#send").onclick = function (){
var data = document.querySelector("textarea#send").value;
sendChannel.send(data);
};
複製程式碼
通訊直接在瀏覽器之間進行,因此即使需要中繼(TURN)伺服器,RTCDataChannel 也可以比 WebSocket快得多。
現實世界中的WebRTC
實際應用中,WebRTC 需要伺服器,無論多簡單,下面四步是必須的:
- 使用者通過交換名字之類的資訊發現對方。
- WebRTC 客戶端應用交換網路資訊。
- 客戶端交換媒體資訊包括視訊格式和解析度。
- WebRTC 客戶端穿透 NAT 閘道器和伺服器。
換句話說,WebRTC 需要四種型別的伺服器端功能:
- 使用者發現和通訊
- 信令
- NAT/防火牆穿透
- 中繼伺服器,防止端到端的通訊失敗
可以說基於 STUN 和TURN協議的 ICE 框架,使得 RTCPeerConnection 處理 NAT 穿透和其他網路難題成為可能。
ICE 框架用於端到端的連線,比如說兩個視訊聊天客戶端。起初,ICE 嘗試通過 UDP 直接連線兩端,這樣可以保證低延遲。在這個過程中,STUN 伺服器有一個簡單的任務:使 NAT 後邊的端能找到它的公網地址和埠(谷歌有多個STUN伺服器,其中一個用在了apprtc.appspot.com例子)。
如果 UDP 傳輸失敗,ICE 會嘗試 TCP:首先是 HTTP,然後才會選擇 HTTPS。如果直接連線失敗,通常因為企業的 NAT 穿透和防火牆,此時 ICE 使用中繼(Relay)伺服器。換句話說,ICE 首先使用STUN 和 UDP 直接連線兩端,失敗之後返回中繼伺服器。‘finding cadidates’
就是尋找網路介面和埠的過程。
安全
實時通訊應用或外掛會在許多方面忽視了安全性:
- 瀏覽器之間、瀏覽器與伺服器之間的音視訊或其他資料沒有加密。
- 應用在使用者沒有察覺的情況下錄製和分發音視訊。
- 惡意軟體或病毒可能入侵了正常的外掛或應用。
WebRTC 的許多特性可以避免這些問題:
* 所有WebRTC元件都必須進行加密,包括信令機制。
* WebRTC 不是一個外掛:它的元件執行在瀏覽器沙盒中,而不是在一個單獨的程式中,元件不需要單獨安裝,並且在瀏覽器更新時都會更新。
- 攝像頭和麥克風的訪問必須經過明確准許,當攝像頭和麥克風執行時,介面上會清楚的顯示出來。
WebRTC是一種非常有趣和強大的技術,用於在瀏覽器之間進行某種形式的實時流。
原文:How JavaScript works: WebRTC and the mechanics of peer to peer networking