本文首發於CSDN網站,下面的版本又經過進一步的修訂。
原文:webpack與browser-sync熱更新原理深度講解
本文包含如下內容:
開發環境頁面熱更新早已是主流,我們不光要吃著火鍋唱著歌,享受熱更新高效率的快感,更要深入下去探求其原理。
要知道,觸類則旁通,常見的需求如賽事網頁推送比賽結果、網頁實時展示投票或點贊資料、線上評論或彈幕、線上聊天室等,都需要藉助熱更新功能,才能達到實時的端對端的極致體驗。
剛好,最近解決webpack-hot-middleware
熱更新延遲問題的過程中,我深入接觸了EventSource技術。遂本文由此開篇,進一步講解webpack-hot-middleware
,browser-sync
背後的技術。
webpack-hot-middleware
webpack-hot-middleware
中介軟體是webpack的一個plugin,通常結合webpack-dev-middleware
一起使用。藉助它可以實現瀏覽器的無重新整理更新(熱更新),即webpack裡的HMR(Hot Module Replacement)。如何配置請參考 webpack-hot-middleware,如何理解其相關外掛請參考 手把手深入理解 webpack dev middleware 原理與相關 plugins。
webpack加入webpack-hot-middleware
後,記憶體中的頁面將包含HMR相關js,載入頁面後,Network欄可以看到如下請求:
__webpack_hmr是一個type
為EventSource的請求, 從Time
欄可以看出:預設情況下,伺服器每十秒推送一條資訊到瀏覽器。
如果此時關閉開發伺服器,瀏覽器由於重連機制,將持續丟擲類似GET http://www.test.com/__webpack_hmr 502 (Bad Gateway)
這樣的錯誤。重新啟動開發伺服器後,重連將會成功,此時便會重新整理頁面。
以上這些便是我們使用時感受到的最初的印象。當然,停留在使用層面不是我們的目標,接下來我們將跳出該中介軟體,講解其所使用到的EventSource
技術。
EventSource
EventSource 不是一個新鮮的技術,它早就隨著H5規範提出了,正式一點應該叫Server-sent events
,即SSE
。
鑑於傳統的通過ajax輪訓獲取伺服器資訊的技術方案已經過時,我們迫切需要一個高效的節省資源的方式去獲取伺服器資訊,一旦伺服器資源有更新,能夠及時地通知到客戶端,從而實時地反饋到使用者介面上。EventSource就是這樣的技術,它本質上還是HTTP,通過response流實時推送伺服器資訊到客戶端。
新建一個EventSource物件非常簡單。
const es = new EventSource(`/message`);// /message是服務端支援EventSource的介面複製程式碼
新建立的EventSource物件擁有如下屬性:
屬性 | 描述 |
---|---|
url(只讀) | es物件請求的伺服器url |
readyState(只讀) | es物件的狀態,初始為0,包含CONNECTING (0),OPEN (1),CLOSED (2)三種狀態 |
withCredentials | 是否允許帶憑證等,預設為false,即不支援傳送cookie |
服務端實現/message
介面,需要返回型別為 text/event-stream
的響應頭。
var http = require(`http`);
http.createServer(function(req,res){
if(req.url === `/message`){
res.writeHead(200,{
`Content-Type`: `text/event-stream`,
`Cache-Control`: `no-cache`,
`Connection`: `keep-alive`
});
setInterval(function(){
res.write(`data: ` + +new Date() + `
`);
}, 1000);
}
}).listen(8888);複製程式碼
我們注意到,為了避免快取,Cache-Control 特別設定成了 no-cache,為了能夠傳送多個response, Connection被設定成了keep-alive.。傳送資料時,請務必保證伺服器推送的資料以 data:
開始,以
結束,否則推送將會失敗(原因就不說了,這是約定的)。
以上,伺服器每隔1s主動向客戶端傳送當前時間戳,為了接受這個資訊,客戶端需要監聽伺服器。如下:
es.onmessage = function(e){
console.log(e.data); // 列印伺服器推送的資訊
}複製程式碼
如下是訊息推送的過程:
你以為es只能監聽message事件嗎?並不是,message只是預設的事件型別。實際上,它可以監聽任何指定型別的事件。
es.addEventListener("####", function(e) {// 事件型別可以隨你定義
console.log(`####:`, e.data);
},false);複製程式碼
伺服器傳送不同型別的事件時,需要指定event欄位。
res.write(`event: ####
`);
res.write(`data: 這是一個自定義的####型別事件
`);
res.write(`data: 多個data欄位將被解析成一個欄位
`);複製程式碼
如下所示:
可以看到,服務端指定event事件名為”####”後,客戶端觸發了對應的事件回撥,同時服務端設定的多個data欄位,客戶端使用換行符連線成了一個字串。
不僅如此,事件流中還可以混合多種事件,請看我們是怎麼收到訊息的,如下:
除此之外,es物件還擁有另外3個方法: onopen()
、onerror()
、close()
,請參考如下實現。
es.onopen = function(e){// 連結開啟時的回撥
console.log(`當前狀態readyState:`, es.readyState);// open時readyState===1
}
es.onerror = function(e){// 出錯時的回撥(網路問題,或者服務下線等都有可能導致出錯)
console.log(es.readyState);// 出錯時readyState===0
es.close();// 出錯時,chrome瀏覽器會每隔3秒向伺服器重發原請求,直到成功. 因此出錯時,可主動斷開原連線.
}複製程式碼
使用EventSource技術實時更新網頁資訊十分高效。實際使用中,我們幾乎不用擔心相容性問題,主流瀏覽器都了支援EventSource,當然,除了掉隊的IE系。對於不支援的瀏覽器,其PolyFill方案請參考HTML5 Cross Browser Polyfills。
CORS
另外,如果需要支援跨域呼叫,請設定響應頭Access-Control-Allow-Origin`: `*`
。
如需支援傳送cookie,請設定響應頭Access-Control-Allow-Origin`: req.headers.origin
和 Access-Control-Allow-Credentials:true
,並且建立es物件時,需要明確指定是否傳送憑證。如下:
var es = new EventSource(`/message`, {
withCredentials: true
}); // 建立時指定配置才是有效的
es.withCredentials = true; // 與ajax不同,這樣設定是無效的複製程式碼
以下是主流瀏覽器對EventSource的CORS的支援:
Firefox | Opera | Chrome | Safari | iOS | Android |
---|---|---|---|---|---|
10+ | 12+ | 26+ | 7.0+ | 7.0+ | 4.4+ |
nginx配置
既然說到了EventSource,便有必要談談遇到的坑,接下來,就說說我遇到的webpack熱更新延遲問題。
如我們所知,webpack藉助webpack-hot-middleware外掛,實現了網頁熱更新機制,正常情況下,瀏覽器開啟 http://localhost:8080 這樣的網頁即可開始除錯。然而實際開發中,由於遠端伺服器需要種cookie登入態到特定的域名上等原因,因此本地往往會用nginx做一層反向代理。即把 www.test.com 的請求轉發到 http://localhost:8080 上(配置過程這裡不詳述,具體請參考Ajax知識體系大梳理-ajax除錯技巧)。轉發過後,發現熱更新便延遲了。
原因是nginx預設開啟的buffer機制快取了伺服器推送的片段資訊,快取達到一定的量才會返回響應內容。只要關閉proxy_buffering即可。配置如下所示:
server {
listen 80;
server_name www.test.company.com;
location / {
proxy_pass http://localhost:8080;
proxy_buffering off;
}
}複製程式碼
至此,EventSource部分便告一段落。學習講究由淺入深,循序漸進。後面我將重點講解的browser-sync
熱更新機制,請耐心細讀。
browser-sync
開發中使用browser-sync
外掛除錯,一個網頁裡的所有互動動作(包括滾動,輸入,點選等等),可以實時地同步到其他所有開啟該網頁的裝置,能夠節省大量的手工操作時間,從而帶來流暢的開發除錯體驗。目前browser-sync
可以結合Gulp
或Grunt
一起使用,其API請參考:Browsersync API。
通過上面的瞭解,我們知道EventSouce
的使用是比較便捷的,那為什麼browser-sync
不使用EventSource技術進行程式碼推送呢?這是因為browser-sync
外掛共做了兩件事:
- 開發更新了一段新的邏輯,伺服器實時推送程式碼改動資訊。資料流:伺服器 —> 瀏覽器,使用EventSource技術同樣能夠實現。
- 使用者操作網頁,滾動、輸入或點選等,操作資訊實時傳送給伺服器,然後再由伺服器將操作同步給其他已開啟的網頁。資料流:瀏覽器 —> 伺服器 —> 瀏覽器,該部分功能EventSource技術已無能為力。
以上,browser-sync
使用WebSocket技術達到實時推送程式碼改動和使用者操作兩個目的。至於它是如何計算推送內容,根據不同推送內容採取何種響應策略,不在本次討論範圍之內。下面我們將講解其核心的WebSocket技術。
WebSocket
WebSocket是基於TCP的全雙工通訊的協議,它與EventSource有著本質上的不同.(前者基於TCP,後者依然基於HTTP) 該協議於2011年被IETF定為標準RFC6455,後被RFC7936補充. WebSocket api也被W3C定為標準。
WebSocket使用和HTTP相同的TCP埠,預設為80, 統一資源標誌符為ws,執行在TLS之上時,預設使用443,統一資源標誌符為wss。它通過101 switch protocol
進行一次TCP握手,即從HTTP協議切換成WebSocket通訊協議。
相對於HTTP協議,WebSocket擁有如下優點:
- 全雙工,實時性更強。
- 相對於http攜帶完整的頭部,WebSocket請求頭部明顯減少。
- 保持連線狀態,不用再驗權了。
- 二進位制支援更強,Websocket定義了二進位制幀,處理更輕鬆。
- Websocket協議支援擴充套件,可以自定義的子協議,如
permessage-deflate
擴充套件。
支援性
優秀技術的落地,調研相容性是必不可少的環節。所幸的是,現代瀏覽器對WebSocket的支援比較友好,如下是PC端相容性:
IE/Edge | Firefox | Chrome | Safari | Opera |
---|---|---|---|---|
10+ | 11+ | 16+ | 7+ | 12.1+ |
如下是mobile端相容性:
iOS Safari | Android | Android Chrome | Android UC | QQ Browser | Opera Mini |
---|---|---|---|---|---|
7.1+ | 4.4+ | 57+ | 11.4+ | 1.2+ | – |
Frame
根據RFC6455文件,WebSocket協議基於Frame而非Stream(EventSource是基於Stream的)。因此其傳輸的資料都是Frame(幀)。想要了解資料的往返,弄懂協議處理過程,Frame的解讀是必不可少。如下便是Frame的結構:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued,if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key,if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+複製程式碼
第一個位元組包含FIN、RSV、Opcode。
-
FIN:size為1bit,標示是否最後一幀。
%x0
表示還有後續幀,%x1
表示這是最後一幀。 -
RSV1、2、3,每個size都是1bit,預設值都是0,如果沒有定義非零值的含義,卻出現了非零值,則WebSocket連結將失敗。
-
Opcode,size為4bits,表示『payload data』的型別。如果收到未知的opcode,連線將會斷開。已定義的opcode值如下:
%x0: 代表連續的幀 %x1: 文字幀 %x2: 二進位制幀 %x3~7: 預留的非控制幀 %x8: 關閉握手幀 %x9: ping幀,後續心跳連線會講到 %xA: pong幀,後續心跳連線會講到 %xB~F: 預留的非控制幀複製程式碼
第二個位元組包含Mask、Payload len。
-
Mask:size為1bit,標示『payload data』是否新增掩碼。所有從客戶端傳送到服務端的幀都會被置為1,如果置1,
Masking-key
便會賦值。//若server是一個WebSocket服務端例項 //監聽客戶端訊息 server.on(`message`, function(msg, flags) { console.log(`client say: %s`, msg); console.log(`mask value:`, flags.masked);// true,進一步佐證了客戶端傳送到服務端的Mask幀都會被置為1 }); //監聽客戶端pong幀響應 server.on(`pong`, function(msg, flags) { console.log(`pong data: %s`, msg); console.log(`mask value:`, flags.masked);// true,進一步佐證了客戶端傳送到服務端的Mask幀都會被置為1 });複製程式碼
-
Payload len:size為7bits,即使是當做無符號整型也只能表示0~127的值,所以它不能表示更大的值,因此規定”Payload data”長度小於或等於125的時候才用來描述資料長度。如果
Payload len==126
,則使用隨後的2bytes(16bits)來儲存資料長度。如果Payload len==127
,則使用隨後的8bytes(64bits)來儲存資料長度。
以上,擴充套件的Payload len可能佔據第三至第四個或第三至第十個位元組。緊隨其後的是”Mask-key”。
- Mask-key:size為0或4bytes(32bits),預設為0,與前面Mask呼應,從客戶端傳送到服務端的幀都包含4bytes(32bits)的掩碼,一旦掩碼被設定,所有接收到的”payload data”都必須與該值以一種演算法做異或運算來獲取真實值。
- Payload data:size為”Extension data” 和 “Application data” 的總和,一般”Extension data”資料為空。
- Extension data:預設為0,如果擴充套件被定義,擴充套件必須指定”Extension data”的長度。
- Application data:佔據”Extension data”之後剩餘幀的空間。
關於Frame的更多理論介紹不妨讀讀 學習WebSocket協議—從頂層到底層的實現原理(修訂版)。
關於Frame的資料幀解析不妨讀讀 WebSocket(貳) 解析資料幀 及其後續文章。
建立連線
瞭解了Frame的資料結構後,我們來實際練習下。瀏覽器上,新建一個ws物件十分簡單。如下:
let ws = new WebSocket(`ws://127.0.0.1:10103/`);// 本地使用10103埠進行測試複製程式碼
新建的WebSocket物件如下所示:
這中間包含了一次Websocket握手的過程,我們分兩步來理解。
第一步,客戶端請求。
這是一個GET請求,主要欄位如下:
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key:61x6lFN92sJHgzXzCHfBJQ==
Sec-WebSocket-Version:13複製程式碼
Connection欄位指定為Upgrade,表示客戶端希望連線升級。
Upgrade欄位設定為websocket,表示希望升級至Websocket協議。
Sec-WebSocket-Key欄位是隨機字串,伺服器根據它來構造一個SHA-1的資訊摘要。
Sec-WebSocket-Version表示支援的Websocket版本。RFC6455要求使用的版本是13。
甚至我們可以從請求截圖裡看出,Origin是file://
,而Host是127.0.0.1:10103
,明顯不是同一個域下,但依然可以請求成功,說明Websocket協議是不受同源策略限制的(同源策略限制的是http協議)。
第二步,服務端響應。
Status Code: 101 Switching Protocols 表示Websocket協議通過101狀態碼進行握手。
Sec-WebSocket-Accept欄位是由Sec-WebSocket-Key欄位加上特定字串”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″,計算SHA-1摘要,然後再base64編碼之後生成的. 該操作可避免普通http請求,被誤認為Websocket協議。
Sec-WebSocket-Extensions欄位表示服務端對Websocket協議的擴充套件。
以上,WebSocket構造器不止可以傳入url,還能傳入一個可選的協議名稱字串或陣列。
ws = new WebSocket(`ws://127.0.0.1:10103/`, [`abc`,`son_protocols`]);複製程式碼
服務端實現
等等,我們慢一點,上面好像漏掉了一步,似乎沒有提到服務端是怎麼實現的。請繼續往下看:
先做一些準備。ws是一個nodejs版的WebSocketServer實現。使用 npm install ws
即可安裝。
var WebSocketServer = require(`ws`).Server,
server = new WebSocketServer({port: 10103});
server.on(`connection`, function(s) {
s.on(`message`, function(msg) { //監聽客戶端訊息
console.log(`client say: %s`, msg);
});
s.send(`server ready!`);// 連線建立好後,向客戶端傳送一條訊息
});複製程式碼
以上,new WebSocketServer()
建立伺服器時如需許可權驗證,請指定verifyClient
為驗權的函式。
server = new WebSocketServer({
port: 10103,
verifyClient: verify
});
function verify(info){
console.log(Object.keys(info));// [ `origin`, `secure`, `req` ]
console.log(info.orgin);// "file://"
return true;// 返回true時表示驗權通過,否則客戶端將丟擲"HTTP Authentication failed"錯誤
}複製程式碼
以上,verifyClient
指定的函式只有一個形參,若為它顯式指定兩個形參,那麼第一個引數同上info,第二個引數將是一個cb
回撥函式。該函式用於顯式指定拒絕時的HTTP狀態碼等,它預設擁有3個形參,依次為:
- result,布林值型別,表示是否通過許可權驗證。
- code,數值型別,若result值為false時,表示HTTP的錯誤狀態碼。
- name,字串型別,若result值為false時,表示HTTP狀態碼的錯誤資訊。
// 若verify定義如下
function verify(info, cb){
//一旦擁有第二個形參,如果不呼叫,預設將通過驗權
cb(false, 401, `許可權不夠`);// 此時表示驗權失敗,HTTP狀態碼為401,錯誤資訊為"許可權不夠"
return true;// 一旦擁有第二個形參,響應就被cb接管了,返回什麼值都不會影響前面的處理結果
}複製程式碼
除了port
和 verifyClient
設定外,其它設定項及更多API,請參考文件 ws-doc。
傳送和監聽訊息
接下來,我們來實現訊息收發。如下是客戶端傳送訊息。
ws.onopen = function(e){
// 可傳送字串,ArrayBuffer 或者 Blob資料
ws.send(`client ready!);
};複製程式碼
客戶端監聽資訊。
ws.onmessage = function(e){
console.log(`server say:`, e.data);
};複製程式碼
如下是瀏覽器的執行截圖。
訊息的內容都在Frames欄,第一條彩色背景的資訊是客戶端傳送的,第二條是服務端傳送的。兩條訊息的長度都是13。
如下是Timing欄,不止是WebSocket,包括EventSource,都有這樣的黃色高亮警告。
該警告說明:請求還沒完成。實際上,直到一方連線close掉,請求才會完成。
關閉連線
說到close,ws的close方法比es的略複雜。
語法:close(short code,string reason);
close預設可傳入兩個引數。code是數字,表示關閉連線的狀態號,預設是1000,即正常關閉。(code取值範圍從0到4999,其中有些是保留狀態號,正常關閉時只能指定為1000或者3000~4999之間的值,具體請參考CloseEvent – Web APIs)。reason是UTF-8文字,表示關閉的原因(文字長度需小於或等於123位元組)。
由於code 和 reason都有限制,因此該方法可能丟擲異常,建議catch下.
try{
ws.close(1001, `CLOSE_GOING_AWAY`);
}catch(e){
console.log(e);
}複製程式碼
ws物件還擁有onclose和onerror監聽器,分別監聽關閉和錯誤事件。(注:EventSource沒有onclose監聽)
擁有的屬性
ws的readyState屬性擁有4個值,比es的readyState的多一個CLOSING的狀態。
常量 | 描述 | EventSource(值) | WebSocket(值) |
---|---|---|---|
CONNECTING | 連線未初始化 | 0 | 0 |
OPEN | 連線已就緒 | 1 | 1 |
CLOSING | 連線正在關閉 | – | 2 |
CLOSED | 連線已關閉 | 2 | 3 |
另外,除了兩種都有的url屬性外,WebSocket物件還擁有更多的屬性。
屬性 | 描述 |
---|---|
binaryType | 被傳輸二進位制內容的型別,有blob,arraybuffer兩種 |
bufferedAmount | 待傳輸的資料的長度 |
extensions | 表示伺服器選用的擴充套件 |
protocol | 指的是構造器第二個引數傳入的子協議名稱 |
檔案上傳
以前一直是使用ajax做檔案上傳,實際上,Websocket上傳檔案也是一把好刀. 其send方法可以傳送String,ArrayBuffer,Blob共三種資料型別,傳送二進位制檔案完全不在話下。
由於各個瀏覽器對Websocket單次傳送的資料有限制,所以我們需要將待上傳檔案切成片段去傳送。如下是實現。
1) html。
<input type="file" id="file"/>複製程式碼
2) js。
const ws = new WebSocket(`ws://127.0.0.1:10103/`);// 連線伺服器
const fileSelect = document.getElementById(`file`);
const size = 1024 * 128;// 分段傳送的檔案大小(位元組)
let curSize, total, file, fileReader;
fileSelect.onchange = function(){
file = this.files[0];// 選中的待上傳檔案
curSize = 0;// 當前已傳送的檔案大小
total = file.size;// 檔案大小
ws.send(file.name);// 先傳送待上傳檔案的名稱
fileReader = new FileReader();// 準備讀取檔案
fileReader.onload = loadAndSend;
readFragment();// 讀取檔案片段
};
function loadAndSend(){
if(ws.bufferedAmount > size * 5){// 若傳送佇列中的資料太多,先等一等
setTimeout(loadAndSend,4);
return;
}
ws.send(fileReader.result);// 傳送本次讀取的片段內容
curSize += size;// 更新已傳送檔案大小
curSize < total ? readFragment() : console.log(`upload successed!`);// 下一步操作
}
function readFragment(){
const blob = file.slice(curSize, curSize + size);// 獲取檔案指定片段
fileReader.readAsArrayBuffer(blob);// 讀取檔案為ArrayBuffer物件
}複製程式碼
3) server(node)。
var WebSocketServer = require(`ws`).Server,
server = new WebSocketServer({port: 10103}),// 啟動伺服器
fs = require(`fs`);
server.on(`connection`, function(wsServer){
var fileName, i = 0;// 變數定義不可放在全域性,因每個連線都不一樣,這裡才是私有作用域
server.on(`message`, function(data, flags){// 監聽客戶端訊息
if(flags.binary){// 判斷是否二進位制資料
var method = i++ ? `appendFileSync` : `writeFileSync`;
// 當前目錄下寫入或者追加寫入檔案(建議加上try語句捕獲可能的錯誤)
fs[method](`./` + fileName, data,`utf-8`);
}else{// 非二進位制資料則認為是檔名稱
fileName = data;
}
});
wsServer.send(`server ready!`);// 告知客戶端伺服器已就緒
});複製程式碼
執行效果如下:
上述測試程式碼中沒有過多涉及伺服器的儲存過程。通常,伺服器也會有快取區上限,如果客戶端單次傳送的資料量超過服務端快取區上限,那麼服務端也需要多次讀取。
心跳連線
生產環境下上傳一個檔案遠比本地測試來得複雜。實際上,從客戶端到服務端,中間存在著大量的網路鏈路,如路由器,防火牆等等。一份檔案的上傳要經過中間的層層路由轉發,過濾。這些中間鏈路可能會認為一段時間沒有資料傳送,就自發切斷兩端的連線。這個時候,由於TCP並不定時檢測連線是否中斷,而通訊的雙方又相互沒有資料傳送,客戶端和服務端依然會一廂情願的信任之前的連線,長此以往,將使得大量的服務端資源被WebSocket連線佔用。
正常情況下,TCP的四次揮手完全可以通知兩端去釋放連線。但是上述這種普遍存在的異常場景,將使得連線的釋放成為夢幻。
為此,早在websocket協議實現時,設計者們便提供了一種 Ping/Pong Frame的心跳機制。一端傳送Ping Frame,另一端以 Pong Frame響應。這種Frame是一種特殊的資料包,它只包含一些後設資料,能夠在不影響原通訊的情況下維持住連線。
根據規範RFC 6455,Ping Frame包含一個值為9的opcode,它可能攜帶資料。收到Ping Frame後,Pong Frame必須被作為響應發出。Pong Frame包含一個值為10的opcode,它將包含與Ping Frame中相同的資料。
藉助ws包,服務端可以這麼來傳送Ping Frame。
wsServer.ping();複製程式碼
同時,需要監聽客戶端響應的pong Frame.
wsServer.on(`pong`, function(data, flags) {
console.log(data);// ""
console.log(flags);// { masked: true,binary: true }
});複製程式碼
以上,由於Ping Frame 不帶資料,因此作為響應的Pong Frame的data值為空串。遺憾的是,目前瀏覽器只能被動傳送Pong Frame作為響應(Sending websocket ping/pong frame from browser),無法通過JS API主動向服務端傳送Ping Frame。因此對於web服務,可以採取服務端主動ping的方式,來保持住連結。實際應用中,服務端還需要設定心跳的週期,以保證心跳連線可以一直持續。同時,還應該有重發機制,若連續幾次沒有收到心跳連線的回覆,則認為連線已經斷開,此時便可以關閉Websocket連線了。
Socket.IO
WebSocket出世已久,很多優秀的大神基於此開發出了各式各樣的庫。其中Socket.IO是一個非常不錯的開源WebSocke庫,旨在抹平瀏覽器之間的相容性問題。它基於Node.js,支援以下方式優雅降級:
- Websocket
- Adobe® Flash® Socket
- AJAX long polling
- AJAX multipart streaming
- Forever Iframe
- JSONP Polling
如何在專案中使用Socket.IO,請參考 第一章 socket.io 簡介及使用。
小結
EventSource,本質依然是HTTP,它僅提供服務端到客戶端的單向文字資料傳輸,不需要心跳連線,連線斷開會持續觸發重連。
WebSocket協議,基於TCP協議,它提供雙向資料傳輸,支援二進位制,需要心跳連線,連線斷開不會重連。
EventSource更輕量和簡單,WebSocket支援性更好(因其支援IE10+)。通常來說,使用EventSource能夠完成的功能,使用WebSocket一樣能夠做到,反之卻不行,使用時若遇到連線斷開或拋錯,請及時呼叫各自的close
方法主動釋放資源。
本問就討論這麼多內容,大家有什麼問題或好的想法歡迎在下方參與留言和評論。
本文作者: louis
本文連結: louiszhai.github.io/2017/04/19/…
參考文章