構建實時Web應用程序有點挑戰,我們需要考慮如何將數據從服務器發送到客戶端。能夠“主動”實現這一功能的技術已經存在了很長時間,并且僅限于兩種通用方法:客戶端請求或服務器請求。
實現這些的幾種方法:
- 長/短輪詢(客戶端拉動)
- WebSockets(服務器推送)
- 服務器發送的事件(服務器推送)
- 客戶端拉取-客戶端以一定的定期間隔向服務器請求更新
- 服務器推送-服務器正在主動將更新推送到客戶端(客戶端拉取的反向操作)
讓我們以一個簡單的用例來比較以上技術,然后選擇合適的技術。
范例:
我們的示例用例非常簡單。我們需要開發一個儀表板Web應用程序,該應用程序可以流轉來自(GitHub / Twitter / .. etc)等網站的活動列表。這個應用程序的目的是從上面列出的各種方法中選擇合適的一種。
1.使用輪詢:
輪詢是一種技術,客戶端通過該技術定期向服務器請求新數據。我們可以通過兩種方式進行輪詢:短輪詢和長輪詢。簡單來說,短輪詢是基于AJAX的計時器,它以固定的延遲進行調用,而長輪詢則基于Comet(即,當服務器事件發生時,服務器將無延遲地將數據發送到客戶端)。兩者都有優點和缺點,并根據用例進行調整。有關深入的詳細信息,請閱讀StackOverflow社區給出的答案。
讓我們看看一個簡單的客戶端長輪詢代碼段的外觀:
/* Client - subscribing to the github events */
subscribe: (callback) => {
const pollUserEvents = () => {
$.ajax({
method: 'GET',
url: 'http://localhost:8080/githubEvents',
success: (data) => {
callback(data) // process the data
},
complete: () => {
pollUserEvents();
},
timeout: 30000
})
}
pollUserEvents()
}
這基本上是一個長輪詢功能,它像往常一樣第一次運行,但是它設置了三十(30)秒的超時,并且在每次對服務器進行Async Ajax調用之后,回調都會再次調用Ajax。
AJAX調用可在HTTP協議上運行,這意味著默認情況下,對同一域的請求應進行多路復用。我們發現這種方法存在一些陷阱。
多路復用(輪詢響應實際上無法同步)
輪詢需要3次往返(TCP SIN,SSL和數據)
超時(如果連接保持空閑時間太長,代理服務器將關閉連接)
您可以在這里閱讀更多關于現實世界的挑戰。
2.使用WebSockets:
WebSocket只是客戶端和服務器之間的持久連接。這是一種通過單個TCP連接提供全雙工通信通道的通信協議。
RFC 6455聲明WebSocket“旨在在HTTP端口80和443上工作,并支持HTTP代理和中介”,從而使其與HTTP協議兼容。為了實現兼容性,WebSocket握手使用HTTP升級標頭將HTTP協議更改為WebSocket協議。HTTP和WebSocket都位于OSI模型的應用程序層,因此依賴于第4層的TCP。
有一個MDN文檔詳細解釋了WebSocket,我也建議您閱讀它。
讓我們看看一個非常簡單的WebSocket客戶端實現的樣子:
$(function () {
// if user is running mozilla then use it's built-in WebSocket
window.WebSocket = window.WebSocket || window.MozWebSocket;
const connection = new WebSocket('ws://localhost:8080/githubEvents');
connection.onopen = function () {
// connection is opened and ready to use
};
connection.onerror = function (error) {
// an error occurred when sending/receiving data
};
connection.onmessage = function (message) {
// try to decode json (I assume that each message
// from server is json)
try {
const githubEvent = JSON.parse(message.data); // display to the user appropriately
} catch (e) {
console.log('This doesn't look like a valid JSON: '+ message.data);
return;
}
// handle incoming message
};
});
如果服務器支持WebSocket協議,它將同意升級,并將通過響應中的Upgrade標頭傳達此信息。
讓我們看看如何在Node.JS(服務器)中實現:
const express = require('express');
const events = require('./events');
const path = require('path');
const app = express();
const port = process.env.PORT || 5001;
const expressWs = require('express-ws')(app);
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/static/index.html'));
});
app.ws('/', function(ws, req) {
const githubEvent = {}; // sample github Event from Github event API https://api.github.com/events
ws.send('message', githubEvent);
});
app.listen(port, function() {
console.log('Listening on', port);
});
一旦我們從GitHub事件API獲得數據,就可以在建立連接后將其流式傳輸到客戶端。對于我們的場景,這種方法也有一些陷阱。
- 使用WebSockets,我們需要自己處理許多由HTTP處理的問題。
- WebSocket是用于傳輸數據的另一種協議,它不會通過HTTP / 2連接自動多路復用。在服務器和客戶端上實現自定義多路復用有點復雜。
- WebSocket是基于幀的,而不是基于流的。當我們打開網絡標簽。您可以看到WebSocket消息在frame中列出。
有關WebSocket的詳細信息,請查看這篇很棒的文章,在這里您可以閱讀有關碎片以及如何在后臺進行處理的更多信息。
3.使用SSE:
SSE是一種機制,一旦建立了客戶端-服務器連接,服務器就可以將數據異步推送到客戶端。然后,只要有新的“大塊”數據可用,服務器就可以決定發送數據。可以將其視為單向發布-訂閱模型。
它還提供了一個標準的JavaScript客戶端API,稱為EventSource,已在大多數現代瀏覽器中實現,作為W3C的HTML5標準的一部分。 Polyfills可用于不支持EventSource API的瀏覽器。
我們可以看到Edge和Opera Mini落后于此實現,對于SSE而言,最重要的案例是針對移動瀏覽器設備,因為這些瀏覽器沒有可行的市場份額。Yaffle是事件源的眾所周知的pollyfill。
由于SSE是基于HTTP的,因此它很自然地與HTTP / 2相適應,并且可以結合使用以實現兩者的最佳選擇:HTTP / 2處理基于多路復用流的有效傳輸層,而SSE為應用程序提供API以實現 推。因此,開箱即用地通過HTTP / 2實現多路復用。連接斷開時會通知客戶端和服務器。通過使用消息維護唯一的ID,服務器可以看到客戶端錯過了n條消息,并在重新連接時發送了未完成消息的積壓。
讓我們看看示例客戶端實現的外觀:
const evtSource = new EventSource('/events');
evtSource.addEventListener('event', function(evt) {
const data = JSON.parse(evt.data);
// Use data here
},false);
此代碼段非常簡單。它連接到我們的源并等待接收消息。現在,示例NodeJS服務器將如下所示。
// events.js
const EventEmitter = require('eventemitter3');
const emitter = new EventEmitter();
function subscribe(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
});
// Heartbeat
const nln = function() {
res.write('\\n');
};
const hbt = setInterval(nln, 15000);
const onEvent = function(data) {
res.write('retry: 500\\n');
res.write(
event: event\\n
);res.write(
data: ${JSON.stringify(data)}\\n\\n
);};
emitter.on('event', onEvent);
// Clear heartbeat and listener
req.on('close', function() {
clearInterval(hbt);
emitter.removeListener('event', onEvent);
});
}
function publish(eventData) {
// Emit events here recieved from Github/Twitter APIs
emitter.emit('event', eventData);
}
module.exports = {
subscribe, // Sending event data to the clients
publish // Emiting events from streaming servers
};
// App.js
const express = require('express');
const events = require('./events');
const port = process.env.PORT || 5001;
const app = express();
app.get('/events', cors(), events.subscribe);
app.listen(port, function() {
console.log('Listening on', port);
});
我們從這種方法中獲得的主要好處是:
- 實施更簡單,數據效率更高
- 開箱即用地通過HTTP / 2自動多路復用
- 將客戶端上數據的連接數限制為一個
如何在SSE,WebSocket和Polling中進行選擇?
經過漫長而詳盡的客戶端和服務器實施之后,SSE似乎是我們解決數據交付問題的最終答案。也有一些問題,但是可以解決。
可以利用服務器發送事件的應用程序的一些簡單示例:
- 實時股價流圖
- 重要事件的實時新聞報道(發布鏈接,推文和圖片)
- 由Twitter的流API提供的實時Github / Twitter儀表板墻
- 監視服務器統計信息(如正常運行時間,運行狀況和正在運行的進程)的監視器。
但是,SSE不僅是其他提供快速更新的方法的可行替代方案。在某些特定情況下,例如在SSE被證明是理想解決方案的情況下,每個人都可以勝過其他人。考慮一個像MMO(大型多人在線)游戲這樣的場景,該場景需要來自連接兩端的大量消息。在這種情況下,WebSockets將壓制SSE。
如果您的用例需要顯示實時的市場新聞,市場數據,聊天應用程序等,例如在我們的案例中,依靠HTTP / 2 + SSE將為您提供有效的雙向通信渠道,同時又能獲得留在其中的好處HTTP世界。
-
Web
+關注
關注
2文章
1282瀏覽量
70841 -
服務器
+關注
關注
13文章
9700瀏覽量
87315
發布評論請先 登錄
Django3如何使用WebSocket實現WebShell
一文詳解WebSocket協議

protel與protues哪個更好些啊?
sse指令集
什么是SSE/SIMD/Speculative execut
什么是Superscalar/SSE/SQRT
什么是WebSocket?進行通信解析 WebSocket 報文及實現

Python如何爬取實時變化的WebSocket數據

WebSocket有什么優點
WebSocket工作原理及使用方法

鴻蒙上WebSocket的使用方法
websocket協議的原理

評論