webpack與browser-sync熱更新原理深度講解

本文首發於CSDN網站,下面的版本又通過進一步的修訂。
原文:webpack與browser-sync熱更新原理深度講解
本文包含以下內容:javascript

  1. webpack-hot-middleware
  2. EventSource
    1. CORS
    2. nginx配置
  3. browser-sync
  4. WebSocket
    1. 支持性
    2. Frame
    3. 創建鏈接
    4. 服務端實現
    5. 發送和監聽消息
    6. 關閉鏈接
    7. 擁有的屬性
    8. 文件上傳
    9. 心跳鏈接
    10. Socket.IO
  5. 小結

開發環境頁面熱更新早已經是主流,咱們不光要吃着火鍋唱着歌,享受熱更新高效率的快感,更要深刻下去探求其原理。html

要知道,觸類則旁通,常見的需求如賽事網頁推送比賽結果、網頁實時展現投票或點贊數據、在線評論或彈幕、在線聊天室等,都須要藉助熱更新功能,才能達到實時的端對端的極致體驗。java

恰好,最近解決webpack-hot-middleware熱更新延遲問題的過程當中,我深刻接觸了EventSource技術。遂本文由此開篇,進一步講解webpack-hot-middlewarebrowser-sync背後的技術。node

webpack-hot-middleware

webpack-hot-middleware中間件是webpack的一個plugin,一般結合webpack-dev-middleware一塊兒使用。藉助它能夠實現瀏覽器的無刷新更新(熱更新),即webpack裏的HMR(Hot Module Replacement)。如何配置請參考 webpack-hot-middleware,如何理解其相關插件請參考 手把手深刻理解 webpack dev middleware 原理與相關 pluginswebpack

webpack加入webpack-hot-middleware後,內存中的頁面將包含HMR相關js,加載頁面後,Network欄能夠看到以下請求:nginx

__webpack_hmr

__webpack_hmr是一個type爲EventSource的請求, 從Time欄能夠看出:默認狀況下,服務器每十秒推送一條信息到瀏覽器。git

hmr每10秒推送一條信息

若是此時關閉開發服務器,瀏覽器因爲重連機制,將持續拋出相似GET http://www.test.com/__webpack_hmr 502 (Bad Gateway) 這樣的錯誤。從新啓動開發服務器後,重連將會成功,此時便會刷新頁面。github

以上這些即是咱們使用時感覺到的最初的印象。固然,停留在使用層面不是咱們的目標,接下來咱們將跳出該中間件,講解其所使用到的EventSource技術。web

EventSource

EventSource 不是一個新鮮的技術,它早就隨着H5規範提出了,正式一點應該叫Server-sent events,即SSEajax

鑑於傳統的經過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() + '\n\n');
    }, 1000);
  }
}).listen(8888);複製代碼

咱們注意到,爲了不緩存,Cache-Control 特別設置成了 no-cache,爲了可以發送多個response, Connection被設置成了keep-alive.。發送數據時,請務必保證服務器推送的數據以 data:開始,以\n\n結束,不然推送將會失敗(緣由就不說了,這是約定的)。

以上,服務器每隔1s主動向客戶端發送當前時間戳,爲了接受這個信息,客戶端須要監聽服務器。以下:

es.onmessage = function(e){
  console.log(e.data); // 打印服務器推送的信息
}複製代碼

以下是消息推送的過程:

response size不斷增長

接收消息

你覺得es只能監聽message事件嗎?並非,message只是缺省的事件類型。實際上,它能夠監放任何指定類型的事件。

es.addEventListener("####", function(e) {// 事件類型能夠隨你定義
  console.log('####:', e.data);
},false);複製代碼

服務器發送不一樣類型的事件時,須要指定event字段。

res.write('event: ####\n');
res.write('data: 這是一個自定義的####類型事件\n');
res.write('data: 多個data字段將被解析成一個字段\n\n');複製代碼

以下所示:

####消息

能夠看到,服務端指定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.originAccess-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能夠結合GulpGrunt一塊兒使用,其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表示這是最後一幀。

  • RSV一、二、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對象

這中間包含了一次Websocket握手的過程,咱們分兩步來理解。

第一步,客戶端請求。

Websocket Request

這是一個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協議)。

第二步,服務端響應。

Websocket Response

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接管了,返回什麼值都不會影響前面的處理結果
}複製代碼

除了portverifyClient設置外,其它設置項及更多API,請參考文檔 ws-doc

發送和監聽消息

接下來,咱們來實現消息收發。以下是客戶端發送消息。

ws.onopen = function(e){
  // 可發送字符串,ArrayBuffer 或者 Blob數據
  ws.send('client ready!);
};複製代碼

客戶端監聽信息。

ws.onmessage = function(e){
  console.log('server say:', e.data);
};複製代碼

以下是瀏覽器的運行截圖。

message

消息的內容都在Frames欄,第一條彩色背景的信息是客戶端發送的,第二條是服務端發送的。兩條消息的長度都是13。

以下是Timing欄,不止是WebSocket,包括EventSource,都有這樣的黃色高亮警告。

Websocket Request

該警告說明:請求還沒完成。實際上,直到一方鏈接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!');// 告知客戶端服務器已就緒
});複製代碼

運行效果以下:

Websocket upload

上述測試代碼中沒有過多涉及服務器的存儲過程。一般,服務器也會有緩存區上限,若是客戶端單次發送的數據量超過服務端緩存區上限,那麼服務端也須要屢次讀取。

心跳鏈接

生產環境下上傳一個文件遠比本地測試來得複雜。實際上,從客戶端到服務端,中間存在着大量的網絡鏈路,如路由器,防火牆等等。一份文件的上傳要通過中間的層層路由轉發,過濾。這些中間鏈路可能會認爲一段時間沒有數據發送,就自發切斷兩端的鏈接。這個時候,因爲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/…

參考文章

相關文章
相關標籤/搜索