Web Sockets定義了一種在經過一個單一的 socket 在網絡上進行全雙工通信的通道。僅僅是傳統的 HTTP 通信的一個增量的提升,尤爲對於實時、事件驅動的應用來講是一個飛躍。 經過Polling(輪詢)、Long-Polling(長輪詢)、Websocket、sse的對比。四種Web即時通訊技術比較它們的實現方式和各自的優缺點。 對比優缺點以下:javascript
# | 輪詢(Polling) | 長輪詢(Long-Polling) | Websocket | sse |
---|---|---|---|---|
通訊協議 | http | http | tcp | http |
觸發方式 | client(客戶端) | client(客戶端) | client、server(客戶端、服務端) | client、server(客戶端、服務端) |
優勢 | 兼容性好容錯性強,實現簡單 | 比短輪詢節約資源 | 全雙工通信協議,性能開銷小、安全性高,可擴展性強 | 實現簡便,開發成本低 |
缺點 | 安全性差,佔較多的內存資源與請求數 | 安全性差,佔較多的內存資源與請求數 | 傳輸數據須要進行二次解析,增長開發成本及難度 | 只適用高級瀏覽器 |
延遲 | 非實時,延遲取決於請求間隔 | 同短輪詢 | 實時 | 非實時,默認3秒延遲,延遲可自定義 |
上面基本上包含了各個實現方式的優勢和缺點,它們基於什麼協議、由那端主動發送數據。html
短輪詢(Polling)的實現思路就是瀏覽器端每隔幾秒鐘向服務器端發送http請求,服務端在收到請求後,不管是否有數據更新,都直接進行響應。在服務端響應完成,就會關閉這個Tcp鏈接,以下圖所示:前端
示例代碼實現以下:java
function Polling() {
fetch(url).then(data => {
// somthing
}).catch(err => {
console.log(err);
});
}
setInterval(polling, 5000);
複製代碼
Tcp
鏈接是很是消耗資源的,服務端響應完成就會關閉這個Tcp
鏈接,下一次請求再次創建Tcp
鏈接。**Alex Russell(Dojo Toolkit 的項目 Lead)**稱這種基於HTTP長鏈接
、無須在瀏覽器端安裝插件的「服務器推」技術爲「Comet」
。 經常使用的COMET分爲兩種:基於HTTP的長輪詢(long-polling)技術,以及基於iframe的長鏈接流(stream)模式。node
客戶端發送請求後服務器端不會當即返回數據,服務器端會阻塞請求鏈接不會當即斷開,直到服務器端有數據更新或者是鏈接超時才返回,客戶端纔再次發出請求新建鏈接、如此反覆從而獲取最新數據。大體效果以下:web
客戶端的代碼以下:express
function LongPolling() {
fetch(url).then(data => {
LongPolling();
}).catch(err => {
LongPolling();
console.log(err);
});
}
LongPolling();
複製代碼
當咱們在頁面中嵌入一個iframe並設置其src時,服務端就能夠經過長鏈接「源源不斷」地向客戶端輸出內容。 例如,咱們能夠向客戶端返回一段script
標籤包裹的javascript
代碼,該代碼就會在iframe中執行。所以,若是咱們預先在iframe
的父頁面中定義一個處理函數process(),而在每次有新數據須要推送時,在該鏈接響應中寫入。那麼iframe中的這段代碼就會調用父頁面中預先定義的process()函數。(是否是有點像JSONP傳輸數據的方式?)後端
// 在父頁面中定義的數據處理方法
function process(data) {
// do something
}
// 建立不可見的iframe
var iframe = document.createElement('iframe');
iframe.style = 'display: none';
// src指向後端接口
iframe.src = '/long_iframe';
document.body.appendChild(iframe);
複製代碼
後端仍是以node爲例瀏覽器
const app = http.createServer((req, res) => {
// 返回數據的方法,將數據拼裝成script腳本返回給iframe
const iframeSend = data => {
let script = `<script type="text/javascript"> parent.process(${JSON.stringify(data)}) </script>`;
res.write(script);
};
res.setHeader('connection', 'keep-alive');
// 注意設置相應頭的content-type
res.setHeader('content-type', 'text/html; charset=utf-8');
// 當有數據更新時,服務端「推送」數據給客戶端
EVENT.addListener(MSG_POST, iframeSend);
req.socket.on('close', () => {
console.log('iframe socket close');
// 注意在鏈接關閉時移除監聽,避免內存泄露
EVENT.removeListener(MSG_POST, iframeSend);
});
});
複製代碼
效果以下: 安全
不過使用iframe有個小瑕疵,所以這個iframe至關於永遠也不會加載完成,因此瀏覽器上會一直有一個loading標誌。
他的優缺點和上面的長輪詢同樣。
WebSocket的一些特性和基礎使用方法在這裏就很少贅述了,請看另外一篇博客webSocket(一) 淺析; 大體代碼以下: 服務端
const express = require('express');
const app = express();
const server = require('http').Server(app);
const WebSocket = require('ws');
const wss = new WebSocket.Server({port: 8080});
wss.on('connection', function connection(ws) {
console.log('server: receive connection');
ws.on('message', function incoming(message) {
console.log('server: recevied: %s', message);
});
ws.send('world');
});
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
app.listen(3000);
複製代碼
客戶端
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
console.log('ws onopen');
ws.send('from client:hello');
};
ws.onmessage = function (e) {
console.log('ws onmessage');
console.log('from server:' + e.data);
}
複製代碼
運行效果以下:
Server-Sent
是HTML5
提出一個標準。由客戶端發起與服務器之間建立TCP
鏈接,而後並維持這個鏈接,直到客戶端或服務器中的任何一方斷開,ServerSent
使用的是"問"+"答"的機制,鏈接建立後瀏覽器會週期性地發送消息至服務器詢問,是否有本身的消息。其實現原理相似於咱們在上一節中提到的基於iframe的長鏈接模式。 HTTP響應內容有一種特殊的content-type —— text/event-stream
,該響應頭標識了響應內容爲事件流,客戶端不會關閉鏈接,而是等待服務端不斷得發送響應結果。 SSE規範比較簡單,主要分爲兩個部分:瀏覽器中的EventSource對象,以及服務器端與瀏覽器端之間的通信協議。
在瀏覽器中能夠經過EventSource構造函數來建立該對象
var source = new EventSource('/sse');
複製代碼
而SSE
的響應內容能夠當作是一個事件流,由不一樣的事件所組成。這些事件會觸發前端EventSource
對象上的方法。
// 默認的事件
source.addEventListener('message', function (e) {
console.log(e.data);
}, false);
// 用戶自定義的事件名
source.addEventListener('my_msg', function (e) {
process(e.data);
}, false);
// 監聽鏈接打開
source.addEventListener('open', function (e) {
console.log('open sse');
}, false);
// 監聽錯誤
source.addEventListener('error', function (e) {
console.log('error');
});
複製代碼
EventSource經過事件監聽的方式來工做。注意上面的代碼監聽了y_msg事件,SSE支持自定義事件,默認事件經過監聽message來獲取數據。實現代碼以下:
客戶端
// 顯示聊天信息
let chat = new EventSource("/chat-room");
chat.onmessage = function (event) {
let msg = event.data;
$(".list-group").append("<li class='list-group-item'>" + msg + "</li>");
// chat.close(); 關閉server-sent event
};
// 自定義事件
chat.addEventListener("myChatEvent", function (event) {
let msg = event.data;
$(".list-group").append("<li class='list-group-item'>" + msg + "</li>");
});
複製代碼
服務端 nodejs
var express = require('express');
var router = express.Router();
router.get('/chat-room', function (req, res, next) {
// 當res.white的數據data 以\n\n結束時 就默認該次消息發送完成,觸發onmessage方法,以\r\n不會觸發onmessage方法
res.header({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
});
// res.white("event: myChatEvent\r\n"); 自定義事件
res.write("retry: 10000\r\n"); // 指定通訊的最大間隔時間
res.write("data: start~~\n\n");
res.end(); // 不加end不會認爲本次數據傳輸結束 會致使不會有下一次請求
});
複製代碼
上面四種Web即時通訊技術比較,能夠從不一樣的角度考慮,它們的優先級是不一樣的,基本上能夠分爲兩大類基於http
和tcp
兩種通訊中的一種。
兼容性考慮:短輪詢>長輪詢>長鏈接SSE>WebSocket
從性能方面考慮:WebSocket>長鏈接SSE>長輪詢>短輪詢
服務端推送:WebSocket>長鏈接SSE>長輪詢