WebRTC給咱們帶來了瀏覽器中的視頻、音頻聊天體驗。但我的認爲,它最實用的特性莫過於DataChannel——在瀏覽器之間創建一個點對點的數據通道。在DataChannel以前,瀏覽器到瀏覽器的數據傳遞一般是這樣一個流程:瀏覽器1發送數據給服務器,服務器處理,服務器再轉發給瀏覽器2。這三個過程都會帶來相應的消耗,佔用服務器帶寬不說,還減緩了消息從發送到接收的時間。其實最理想的方式就是瀏覽器1直接與瀏覽2進行通訊,服務器不須要參與其中。WebRTC DataChannel就提供了這樣一種方式。前端
若是對WebRTC和DataChannel不太瞭解的同窗,能夠先閱讀以下文章:
- WebRTC的RTCDataChannel
- 使用WebRTC搭建前端視頻聊天室——信令篇
- 使用WebRTC搭建前端視頻聊天室——入門篇git
固然服務器徹底不參與其中,顯然是不可能的,用戶須要經過服務器上存儲的信息,才能肯定須要和誰創建鏈接。這裏經過一個故事來說述創建鏈接的過程:github
一些背景:
- 老劉和老姚都住在同一個小區但不一樣的片區,小區很破舊,沒有電話
- 片區相互隔離且片區門口有個保安,保安只認識本身片區的人,遇到不認識的人就須要查詢憑證才能經過,而憑證須要找物業才能肯定
- 門衛老大爺認識小區裏的全部人可是不知道都住哪,有什麼消息均可以在出入小區的時候代爲傳達web
如今,老劉據說老姚釣魚技術高超,想和老姚討論釣魚技巧。只要老劉和老姚相互之間知道對方的門牌號以及憑證,就能夠串門了:面試
老劉和老姚相互之間知道了對方的門牌號和小區出入憑證,他們相互之間有什麼須要交流的直接串門就好了,消息再也不須要門衛老大爺來代爲傳達了chrome
咱們把角色作一個映射:
- 老劉:瀏覽器1
- 老姚:瀏覽器2
- 片區:不一樣網段
- 保安:防火牆
- 片區憑證:ICE candidate
- 物業:ICE server
- 門牌號:session description
- 門衛老大爺:server瀏覽器
因而乎故事就變成了這樣:服務器
這樣,就創建了一個點對點的信道,流程以下所示:websocket
老劉和老姚已經能夠相互串門了,通過一段時間的交流感情愈來愈深。老姚的親友送了20斤葡萄給老姚,老姚決定送10斤給老劉。老姚畢竟年事已高,不可能一次帶10斤。因而乎,老姚將葡萄分紅了10份,每次去老劉家串門就送一份過去。session
這裏能夠作以下類比:
1. 10斤葡萄:一個文件(儘管文件分片沒有意義,葡萄分開還能夠單獨吃,可是實在找不到啥好的比喻了)
2. 分紅10份:將文件分片,轉成多個chunk
3. 老姚一次只能帶一斤:datachannel每次傳輸的數據量不宜太大(找到最合適的大小)
這其實就是經過datachannel傳輸文件的方式,首先將文件分片,而後逐個發送,最後再統一的進行組合成一個新的文件
經過HTML5的File API能夠將type爲file的input選中的文件讀取出來,並轉換成data url字符串。這也就爲咱們提供了很方便的分片方式:
var reader = new window.FileReader(file); reader.readAsDataURL(file); reader.onload = function(event, text) { chunkify(event.target.result);//將數據分片 };
經過datachannel發送的分片數據,咱們須要將其進行組合,因爲是data url字符串,在接收到全部包以後進行拼接就能夠了。拼接完成後就獲得了一個文件完整的data url字符串,那麼咱們如何將這個字符串轉換成文件呢?
既然是個dataurl,咱們直接將其賦值給window.location.href
天然能夠下載,可是這樣下載是無法設定下載後的文件名的,這想想都蛋疼
這個原理和跳轉下載相似,都是使用dataurl自己的特性,經過建立一個a標籤,將dataurl字符串賦值給href屬性,而後使用download肯定下載後的文件名,就能夠完成下載了。可是很快又有新問題了,稍微大一點的文件下載的時候頁面崩潰了。這是由於dataurl有大小限制
其實能夠經過給a標籤建立blob url的方式來進行下載,這個沒有大小限制。可是咱們手上是dataurl,因此須要先進行轉換:
function dataURItoBlob(dataURI, dataTYPE) { var binary = atob(dataURI.split(',')[1]), array = []; for (var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i)); return new Blob([new Uint8Array(array)], { type: dataTYPE }); }
得到blob後,咱們就能夠經過URL API來下載了:
var a = document.createElement("a"); document.body.appendChild(a); a.style = "display: none"; var blob = dataURItoBlob(data, 'octet/stream'); var url = window.URL.createObjectURL(blob); a.href = url; a.download = filename; a.click(); !moz && window.URL.revokeObjectURL(url); a.parentNode.removeChild(a);
這裏有幾個點:
1. datachannel實際上是能夠直接傳送blob的,可是隻有ff支持,因此傳data url
2. chrome下載是直接觸發的,不會進行詢問,firefox會先詢問後下載,在詢問過程當中若是執行了revokeObjectURL
,下載就會取消,囧
如咱們所知,WebRTC最有特色的地方實際上是能夠傳輸getUserMedia得到的視頻、音頻流,來實現視頻聊天。但事實上咱們的使用習慣來看,通常人不會一開始就打開視頻聊天,並且視頻聊天時很消耗內存的(32位機上一個鏈接至少20M左右好像,也有可能有出入)。因此常見的需求是,先創建一個包含datachannel的鏈接用於傳輸數據,而後在須要時升級成能夠傳輸視頻、音頻。
看看咱們以前傳輸的session description,它其實來自Session Description Protocol。能夠看到wiki上的介紹:
The Session Description Protocol (SDP) is a format for describing streaming media initialization parameters.
這意味着什麼呢?咱們以前創建datachannel是沒有加視頻、音頻流的,而這個流的描述是寫在SDP裏面的。如今咱們須要傳輸視頻、音頻,就須要添加這些描述。因此就得從新得到SDP,而後構建offer和answer再傳輸一次。傳輸的流程和以前同樣,沒什麼區別。但這一次,咱們不須要傳輸任何的ice candidate,這裏我曾經遇到了坑,通過國外大大的點撥才明白過來。
from mattm: You do not need to send ICE candidates on an already established peer connection. The ICE candidates are to make sure the two peers can establish a connection through their potential NAT and firewalls. If you can already send data on the peer connection, ICE candidates will not do anything.
我將datachannel和websocket組合,實現了一個構建點對點鏈接的庫Peertc,它提供很是簡潔的方式來創建鏈接和發送數據、文件和視頻/音頻流,詳情見github。走過路過的記得star一下哦,有什麼bug也很是但願可以提出來。
WebRTC的點對點方式可以運用在不少場景:
- 如web qq這種Web IM工具,這就不說了
- 如象棋這種雙人對戰遊戲,每一步的數據服務器時不關心的,因此徹底能夠點對點發送
- 一對一在線面試、在線教育,這實際上是即時通訊的一個業務方向
- 視頻裸(),當我沒說
就醬,另外打個廣告及拉點搜索引擎權重:個人博客