Web 實時推送技術的總結
前言
隨著 Web 的發展,使用者對於 Web 的實時推送要求也越來越高 ,比如,工業執行監控、Web 線上通訊、即時報價系統、線上遊戲等,都需要將後臺發生的變化主動地、實時地傳送到瀏覽器端,而不需要使用者手動地重新整理頁面。本文對過去和現在流行的 Web 實時推送技術進行了比較與總結。
本文完整的原始碼請猛戳Github部落格,紙上得來終覺淺,建議大家動手敲敲程式碼。
一、雙向通訊
HTTP 協議有一個缺陷:通訊只能由客戶端發起。舉例來說,我們想了解今天的天氣,只能是客戶端向伺服器發出請求,伺服器返回查詢結果。HTTP 協議做不到伺服器主動向客戶端推送資訊。這種單向請求的特點,註定瞭如果伺服器有連續的狀態變化,客戶端要獲知就非常麻煩。在WebSocket協議之前,有三種實現雙向通訊的方式:輪詢(polling)、長輪詢(long-polling)和iframe流(streaming)
1.輪詢(polling)
輪詢是客戶端和伺服器之間會一直進行連線,每隔一段時間就詢問一次。其缺點也很明顯:連線數會很多,一個接受,一個傳送。而且每次傳送請求都會有Http的Header,會很耗流量,也會消耗CPU的利用率。
- 優點:實現簡單,無需做過多的更改
- 缺點:輪詢的間隔過長,會導致使用者不能及時接收到更新的資料;輪詢的間隔過短,會導致查詢請求過多,增加伺服器端的負擔
// 1.html <div id="clock"></div> <script> let clockdiv = document.getElementById('clock'); setInterval(function(){ let xhr = new XMLHttpRequest; xhr.open('GET','/clock',true); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ console.log(xhr.responseText); clockdiv.innerhtml = xhr.responseText; } } xhr.send(); },1000); </script> //輪詢 服務端 let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.get('/clock',function(req,res){ res.end(new Date().toLocaleString()); }); app.listen(8080);
2.長輪詢(long-polling)
長輪詢是對輪詢的改進版,客戶端傳送HTTP給伺服器之後,看有沒有新訊息,如果沒有新訊息,就一直等待。當有新訊息的時候,才會返回給客戶端。在某種程度上減小了網路頻寬和CPU利用率等問題。由於http資料包的頭部資料量往往很大(通常有400多個位元組),但是真正被伺服器需要的資料卻很少(有時只有10個位元組左右),這樣的資料包在網路上週期性的傳輸,難免對網路頻寬是一種浪費。
- 優點:比 Polling 做了優化,有較好的時效性
- 缺點:保持連線會消耗資源; 伺服器沒有返回有效資料,程式超時。
// 2.html 服務端程式碼同上 <div id="clock"></div> <script> let clockdiv = document.getElementById('clock') function send() { let xhr = new XMLHttpRequest() xhr.open('GET', '/clock', true) xhr.timeout = 2000 // 超時時間,單位是毫秒 xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { //如果返回成功了,則顯示結果 clockdiv.innerhtml = xhr.responseText } send() //不管成功還是失敗都會發下一次請求 } } xhr.ontimeout = function() { send() } xhr.send() } send() </script>
3.iframe流(streaming)
iframe流方式是在頁面中插入一個隱藏的iframe,利用其src屬性在伺服器和客戶端之間建立一條長連線,伺服器向iframe傳輸資料(通常是html,內有負責插入資訊的JavaScript),來實時更新頁面。
- 優點:訊息能夠實時到達;瀏覽器相容好
- 缺點:伺服器維護一個長連線會增加開銷;IE、chrome、Firefox會顯示載入沒有完成,圖示會不停旋轉。
// 3.html <body> <div id="clock"></div> <iframe src="/clock" style="display:none"></iframe> </body> //iframe流 let express = require('express') let app = express() app.use(express.static(__dirname)) app.get('/clock', function(req, res) { setInterval(function() { let date = new Date().toLocaleString() res.write(` <script type="text/JavaScript"> parent.document.getElementById('clock').innerhtml = "${date}";//改變父視窗dom元素 </script> `) }, 1000) }) app.listen(8080)
上述程式碼中,客戶端只請求一次,然而服務端卻是源源不斷向客戶端傳送資料,這樣伺服器維護一個長連線會增加開銷。
以上我們介紹了三種實時推送技術,然而各自的缺點很明顯,使用起來並不理想,接下來我們著重介紹另一種技術--websocket,它是比較理想的雙向通訊技術。
二、WebSocket
1.什麼是websocket
WebSocket是一種全新的協議,隨著html5草案的不斷完善,越來越多的現代瀏覽器開始全面支援WebSocket技術了,它將TCP的Socket(套接字)應用在了webpage上,從而使通訊雙方建立起一個保持在活動狀態連線通道。
一旦Web伺服器與客戶端之間建立起WebSocket協議的通訊連線,之後所有的通訊都依靠這個專用協議進行。通訊過程中可互相傳送jsON、XML、html或圖片等任意格式的資料。由於是建立在HTTP基礎上的協議,因此連線的發起方仍是客戶端,而一旦確立WebSocket通訊連線,不論伺服器還是客戶端,任意一方都可直接向對方傳送報文。
初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協議,為什麼還需要另一個協議?
2.HTTP的侷限性
- HTTP是半雙工協議,也就是說,在同一時刻資料只能單向流動,客戶端向伺服器傳送請求(單向的),然後伺服器響應請求(單向的)。
- 伺服器不能主動推送資料給瀏覽器。這就會導致一些高階功能難以實現,諸如聊天室場景就沒法實現。
3.WebSocket的特點
- 支援雙向通訊,實時性更強
- 可以傳送文字,也可以傳送二進位制資料
- 減少通訊量:只要建立起WebSocket連線,就希望一直保持連線狀態。和HTTP相比,不但每次連線時的總開銷減少,而且由於WebSocket的首部資訊很小,通訊量也相應減少了
相對於傳統的HTTP每次請求-應答都需要客戶端與服務端建立連線的模式,WebSocket是類似Socket的TCP長連線的通訊模式,一旦WebSocket連線建立後,後續資料都以幀序列的形式傳輸。在客戶端斷開WebSocket連線或Server端斷掉連線前,不需要客戶端和服務端重新發起連線請求。在海量併發和客戶端與伺服器互動負載流量大的情況下,極大的節省了網路頻寬資源的消耗,有明顯的效能優勢,且客戶端傳送和接受訊息是在同一個持久連線上發起,實時性優勢明顯。
接下來我看下websocket如何實現客戶端與服務端雙向通訊:
// websocket.html <div id="clock"></div> <script> let clockDiv = document.getElementById('clock') let socket = new WebSocket('ws://localhost:9999') //當連線成功之後就會執行回撥函式 socket.onopen = function() { console.log('客戶端連線成功') //再向服務 器傳送一個訊息 socket.send('hello') //客戶端發的訊息內容 為hello } //繫結事件是用加屬性的方式 socket.onmessage = function(event) { clockDiv.innerHTML = event.data console.log('收到伺服器端的響應', event.data) } </script> // websocket.js let express = require('express') let app = express() app.use(express.static(__dirname)) //http伺服器 app.listen(3000) let WebSocketServer = require('ws').Server //用ws模組啟動一個websocket伺服器,監聽了9999埠 let wsServer = new WebSocketServer({ port: 9999 }) //監聽客戶端的連線請求 當客戶端連線伺服器的時候,就會觸發connection事件 //socket代表一個客戶端,不是所有客戶端共享的,而是每個客戶端都有一個socket wsServer.on('connection', function(socket) { //每一個socket都有一個唯一的ID屬性 console.log(socket) console.log('客戶端連線成功') //監聽對方發過來的訊息 socket.on('message', function(message) { console.log('接收到客戶端的訊息', message) socket.send('伺服器迴應:' + message) }) })
pixabayhttps://www.wode007.com/sites/73237.html wallhavenhttps://www.wode007.com/sites/73236.html
三、Web 實時推送技術的比較
方式 | 型別 | 技術實現 | 優點 | 缺點 | 適用場景 |
---|---|---|---|---|---|
輪詢Polling | client→server | 客戶端迴圈請求 | 1、實現簡單 2、 支援跨域 | 1、浪費頻寬和伺服器資源 2、 一次請求資訊大半是無用(完整http頭資訊) 3、有延遲 4、大部分無效請求 | 適於小型應用 |
長輪詢Long-Polling | client→server | 伺服器hold住連線,一直到有資料或者超時才返回,減少重複請求次數 | 1、實現簡單 2、不會頻繁發請求 3、節省流量 4、延遲低 | 1、伺服器hold住連線,會消耗資源 2、一次請求資訊大半是無用 | WebQQ、Hi網頁版、Facebook IM |
長連線iframe | client→server | 在頁面裡嵌入一個隱蔵iframe,將這個 iframe 的 src 屬性設為對一個長連線的請求,伺服器端就能源源不斷地往客戶端輸入資料。 | 1、資料實時送達 2、不發無用請求,一次連結,多次“推送” | 1、伺服器增加開銷 2、無法準確知道連線狀態 3、IE、chrome等一直會處於loading狀態 | Gmail聊天 |
WebSocket | server⇌client | new WebSocket() | 1、支援雙向通訊,實時性更強 2、可傳送二進位制檔案3、減少通訊量 | 1、瀏覽器支援程度不一致 2、不支援斷開重連 | 網路遊戲、銀行互動和支付 |
綜上所述:Websocket協議不僅解決了HTTP協議中服務端的被動性,即通訊只能由客戶端發起,也解決了資料同步有延遲的問題,同時還帶來了明顯的效能優勢,所以websocket
是Web 實時推送技術的比較理想的方案,但如果要相容低版本瀏覽器,可以考慮用輪詢來實現。