WebP2P 讓你的直播免流


你在下載的時候,有沒有體驗過 P2P 下載,能夠讓你的網速從 10KB 直接提升到 10MB?

你在企業內傳輸文件的時候,有沒有體驗過文件秒傳?

你在看直播的時候,想不想用別人的流量看直播呢?

...

能做到上面這些場景的技術,叫做 P2P。P2P 技術中,最出名的叫做 WebRTC。WebRTC 是一個含金量非常高的技術。做好的話你可以養活一家公司,做不好,那就只能是一個 demo。

WebRTC 雖然能做很多事,但是並不是所有場景都適合。最大的使用場景是 兩個終端在同一個 NAT 內,簡單來說,都在一個 wifi 內。這個場景中,最顯著的效果就是帶寬無限並且高速,你走的就是內部的線路,根本不消耗運營商的流量。

P2P 技術在基於 WebRTC 標準下,可以做很多事情:

  • 錄屏應用
  • APP Drop
  • 視頻直播
  • ...

作為 Web 開發,WebRTC 又能夠給前端賦能些什麼呢?

在了解這些基本內容後,接下來,我們會從底層一步一步介紹一下 P2P 在 Web 直播的應用。

P2P 穿透

P2P 穿透也可以叫做 NAT 穿透,這是 P2P 最大的一個難點。為了解決 ipv4 不夠用,推出了 NAT 技術。NAT (Network Address Translation)是用來將內網私有 ip,轉化為公有 ip。簡單點,就是讓很多台電腦公用同一個 IP。但是 NAT 有個非常重要的點:

NAT 不允許外網主機主動訪問內網主機。

這個不允許訪問的機制也有很多種,根據這些特性,我們可以將 NAT 分為多種:

  • 完整錐型NAT(Full Cone NAT)
  • 受限錐型NAT(Restricted Cone NAT):
  • 埠受限型NAT(Port Restricted Cone NAT)
  • 對稱型NAT(Symmetric NAT)

一般情況下,前面三種 NAT 是可以穿透的,但是,對稱型NAT 無法穿透。具體內容,大家網上搜一搜 NAT 穿透,資源應該很多。在穿透時,我們不僅需要考慮 NAT 還需要考慮到集群機器的防火牆設定,如果防火牆限制了 UDP 打洞,那麼我們還需要切換為 TCP 打洞(TCP 打洞一般會慢一點)。

總的來說,我們穿透時需要考慮的問題就有:

  • NAT 類型
  • 兩端處在 NAT 的位置:都在一層 NAT 後還是多層 NAT 後...
  • 防火牆連接協議的設定

這些問題一旦組合起來,這個複雜度就是 N*N 的關係了。如果搭建 p2p 每次都需要從頭解決這個內容,P2P 也不會像現在發展的這麼好了。WebRTC 就是用來解決這一問題的標準模板,通過 STUN/TURN Server 來實現打洞穿透。

WebRTC 打洞流程

這裡,我們按照一個比較常見的情況作為模板講解一下。兩端都位於 NAT 層背後,並且,NAT 是可以穿透的 Full Cone NAT 類型。具體穿透流程如下:

  • A 和 B 需要和 STUN 伺服器建立連接,獲得 A/B 的公網 ip:port 和私網 ip:port。
  • B 往 A 發送一個打洞包,此時,已經在 B 的 NAT 上留下到 A 的 打洞 session。但是,由於該包沒有 A NAT 的 session 記錄,會被 A 拒絕掉。
  • A 往 B 發送一個打洞包,該包會在 A 的 NAT 上增加 B 的 session。此時,由於 B 的 NAT 上存在 A 的 session,該包是可以直接被 B 的 NAT 通過的。
  • 打洞完成

在 WebRTC 完成這裡流程的 API 是:RTCPeerConnection。通過自建的一個中間 Server,來交換指定的 SDP 和 candidate。

SDP 是當前 Point 的一些基本描述信息,當前 WebRTC 版本 ICE 的描述信息,以及,對已經連接的 ICE 內容的描述,比如 video/audio 信息。SDP 這一環節,其實就是告訴了哪兩個 Point 會進行連接。本身和打洞並沒有太大的關聯。具體內容可以參考:SDP antonomy

candidate 則是打洞的關鍵信息,裡面會包含當前 Point 的內外網 ip:port,以及防火牆設定規則 tcp/upd。這裡有一點需要注意的是,一個 point 為了能夠提高 NAT 打洞的成功率,會產生多個 candidate。這裡主要取決於幾個 candidate 裡面幾個基本參數:

  • sdpMid 用來指定該次 candidate 的傳輸的 mediaStream 內容。例如,videoaudio
  • protocol:指定連接的協議,tcpudp
  • type: 表示能夠穿透 NAT 的類型

    • host: 能夠直連,或者在同一個 NAT 內
    • srflx/prflx: 外網直連通道,STUN 已經幫忙打了一個洞。如果兩端不在同一個 NAT 裡面,會用到該內容.
    • relay:是 TURN 伺服器的中轉通道。針對的是,對稱型的 NAT,數據交換隻能走中間的 server。

不過,srflx/prflx 類型的 candidate 只會在你初始化 RTCPeerConnection時,傳入 iceServers 的 STUN 伺服器 URL 時,才會獲得。

config = { "iceServers": [{ "urls": ["stun:stun.l.google.com:19302"] }], "iceTransportPolicy": "all", "iceCandidatePoolSize": "0" }

在 webRTC 代碼層面,我們並不需要額外針對 candidate 做邏輯處理。我們只需要將 candidate 傳給另外一端,通過 addIceCandidate() 註冊到 RTC 內部即可。

local.addIceCandidate(remoteCandidate).then(e=>{ console.log("add success");})

通過雲伺服器完成 candidate 和 sdp 信息的交換後,我們就已經做完了 RTC 連接的必要準備。剩下的就是在連接建立完成之後做的狀態監聽和其他擴展事情。

連接狀態判斷

這裡面最大的一個問題在於,我們完成數據添加之後,怎麼判斷 P2P 是否連接上。WebRTC 提供了我們 7 個基本的事件監聽:

attribute EventHandler onnegotiationneeded;attribute EventHandler onicecandidate;attribute EventHandler onicecandidateerror;attribute EventHandler onsignalingstatechange;attribute EventHandler oniceconnectionstatechange;attribute EventHandler onicegatheringstatechange;attribute EventHandler onconnectionstatechange;

通過 onconnectionstatechange 就可以得到連接狀態的變化。直接綁定該方法,即可獲得相關內容:

peer.addEventListener(connectionstatechange,event=>{ // get the state from event},false);

其能夠提供的事件回調信息,可以直接參考:connection states。裡面,我們只需要判斷狀態是否是 connected,來決定該次連接是否成功。

如果不成功,我們可以直接從 onicecandidateerror 裡面獲得相關的錯誤信息,具體可以參考:ICE gather error。當然,在連接過程中,也可以直接從 Promise 中,獲得連接失敗的信息,這部分內容可以直接參考:RTC Error。

上面整個代碼流程可以直接參考:webRTC trickle candidate

DataChannel 數據穿洞

打洞過程是 WebRTC 最基礎的一步,如果連這一步都沒成功,那麼後面就需要做一些其它適配的兼容。比如,直接通過 CDN 拉去資源。WebRTC 打洞成功後,我們就可以利用這個打洞包,根據用戶的種子資源數、上行帶寬、下載進度來判斷 P2P 傳輸的資源。

WebRTC 原生提供了 RTC Media API、RTCDataChannel、DTMF 這三個傳輸通道。Media 能夠直接結合 getUserMedia API 傳輸當前攝像頭和麥克獲取的音視頻。並且 Chrome 瀏覽器在底層內嵌了很多功能強大的編碼器,這裡可以直接參考:webRTC FRQ.

不過,經過測試 WebRTC 回聲消除和雙端同時對話的效果並不是特別好。這塊,大家可以考慮一下,能不能直接在底層替換編碼器或者購買其他服務。

Media 和 DTMF 通常都需要建立在 getUserMedia 的前提下,但是,IOS11 並不支持,它只支持 DataChannel 傳輸數據的 API。所以,這裡,我們只會針對 DataChannel 來做一些講解。如何通過 DataChannel 來傳輸你的自定義文件內容。

DataChannel 是 PeerConnection 的一個拓展 API,可以直接通過 createDataChannel 來創建一個 SCTP 通道。SCTP 是一種高效的幀傳輸協議,它和 TCP/UDP 是在同一層的,集中了兩者之間的優勢。我們只需要在發送端創建 Channel,接收端直接監聽 ondatachannel 事件即可。

let senderChannel = localPeer.createDataChannel(dataChannel);senderChannel.onopen = ()=>{ if(senderChannel.readyState === open){ senderChannel.send(someBuffer); }};senderChannel.birnayType = arrayBuffer;remotePeer.ondatachannel = function(event){ // receive data from event.channel remotePeer = event.channel; remotePeer.binaryType = arraybuffer; remotePeer.onmessage = onReceiveMessageCallback;}

RTCDataChannel 一共可以發送兩種數據:String 和 Binary(也可以叫做 ArrayBuffer,ArrayBufferView or Blob) 具體直接參考:dataView。

Channel 具體的用法其實就和 WebSocket 一樣,通過 send 來傳遞相關的信息,再監聽 onmessage 事件獲得數據的回調。

上面的流程大致的覆蓋了打洞的雲端流程,比如穿透 NAT 層,P2P 數據 Channel 建立。不過,中間還有很多細節並沒有解決清楚:

  • 怎麼找到最優的 Point
  • 需要傳輸的數據是哪些
  • 傳輸數據的進度控制

而這些問題就需要落地到具體的業務當中了。因為現在直播行業非常的火熱,本人也在該行業裡面摸爬滾打,了解到該行業一些基本的痛點。最大的就是非常吃帶寬,為了解決這一問題,我們完全可以利用 P2P 來做直播帶寬的節省。

但是,說起來很容易,怎麼做這才是關鍵?

不過,對於 Web 開發來說,這裡只介紹一下的基本思路。在 Web 直播中比較流行的是通過 http-chunked 模式來實現直播。而 http-chunked 協議很難得到具體播放的進度,那我們就需要一個能夠很容易獲得播放進度的協議--切片協議。

對於切片協議而言,最為突出的就是 HLS,但是它的延時性過高,沒辦法滿足直播低延時的問題。針對這個點,MPEG 提出了一個 DASH 協議,來作為直播內容協議的補充。直播 DASH 是基於 HTTP-URL 的動態直播協議,通過 URL 上面的時間戳就很容易做到對流的時間記錄,整個端的 P2P 流程圖為:

雲端這一步,就主要在 Segment Protocol 做的事情,右邊的則是本地播放的 IS/MS 處理。而如果你能夠在雲上做好這一整套流程,比如:

  • 確定最佳上行 Peer
  • 維護一整套種子和資源的雲端管理
  • Peer 糾錯機制
  • WebRTC 和 DASH 的最佳切換

那麼,你的 P2P 功能和編碼能力應該比一般程序員高太多了。

入門 Tip

事情需要一點一點做,飯需要一口一口的吃,DASH 由於協議比較複雜,推薦大家先從 HTTP-Chunked 協議入手,自己先搭建一個直播的 DEMO 試試水,這裡推薦一下 HTTPLIVE 庫。

另外,如果讀者對前端音視頻很感興趣,不甘心只做一個純純的 UI 工程師的話,可以直接關注我的公眾號前端小吉米,輸入 MSE 加入 前端音視頻的交流小組

更多可以參考:

villianHR個人首頁 | villianHRwww.villainhr.com


推薦閱讀:

WebRTC有前途嗎?
Webrtc的多路事件分離器及網路API
基於 WebRTC 的視頻聊天技術在 iOS 端的實現
教你如何結合WebRTC與TensorFlow實現圖像檢測(上篇)
互動式連接建立(ICE)

TAG:WebRTC | 前端開發 | 直播 |