服務器推送消息的幾種方式
通常情況下,在 bs 架構(gòu)的體系中,服務器想要向客戶端推送消息,主要有以下幾種方式:
- 短輪詢:客戶端定時向服務器請求數(shù)據(jù),服務器立即返回響應信息。
- 長輪詢:客戶端向服務器請求數(shù)據(jù),服務器保持連接,直到有新消息才返回響應信息并關閉連接。
- 流信息:客戶端向服務器請求數(shù)據(jù),服務器聲明要發(fā)送一個數(shù)據(jù)流,連續(xù)不斷地發(fā)送數(shù)據(jù)給客戶端。
- WebSocket:客戶端和服務器建立一個雙向通信的連接,可以互相發(fā)送和接收數(shù)據(jù)。
這幾種推送消息的方式,實現(xiàn)原理的區(qū)別如下:
- 短輪詢:不需要特殊的技術支持,只需要客戶端和服務器之間能夠進行 HTTP 請求和響應即可。
- 長輪詢:不需要特殊的技術支持,只需要客戶端和服務器能夠進行 HTTP 請求和響應即可,但是服務器要能夠保持連接并及時返回新消息。
- 流信息:需要客戶端支持 EventSource 接口,需要服務器能夠發(fā)送
Content-Type: text/event-stream的響應,并且遵循一定的格式規(guī)范。 - WebSocket:需要客戶端支持 WebSocket 接口,需要服務器能夠升級 HTTP 連接為 WebSocket 連接,并且實現(xiàn) WebSocket 協(xié)議。
那么這幾種方式,各自的優(yōu)缺點如下:
- 短輪詢:優(yōu)點是實現(xiàn)簡單,不需要特殊的技術支持;缺點是效率低,浪費資源,不能實時推送消息。
- 長輪詢:優(yōu)點是能夠?qū)崟r推送消息,減少無效請求;缺點是服務器壓力大,需要保持連接并及時返回新消息。
- 流信息:優(yōu)點是能夠?qū)崟r推送消息,節(jié)省資源,避免輪詢的缺陷;缺點是只能單向通信,需要客戶端支持 EventSource 接口。
- WebSocket:優(yōu)點是能夠?qū)崿F(xiàn)雙向通信,效率高,節(jié)省資源;缺點是需要客戶端和服務器都支持 WebSocket 接口和協(xié)議。
這些方式,各自適合以下使用場景:
- 短輪詢:適合數(shù)據(jù)更新頻率不高,實時性要求不高的場景,例如新聞、博客等。
- 長輪詢:適合數(shù)據(jù)更新頻率較高,實時性要求較高的場景,例如聊天、通知等。
- 流信息:適合數(shù)據(jù)更新頻率較高,實時性要求較高的場景,且只需要單向通信的場景,例如股票行情、在線直播等。
- WebSocket:適合數(shù)據(jù)更新頻率較高,實時性要求較高的場景,且需要雙向通信的場景,例如在線游戲、視頻會議等。
用 deno 實現(xiàn)一個 Server-sent events 接口
了解完以上這些前置信息后,回到我們這篇文章想要談論的正題上來。
我們知道,Server-sent events 是一種通過 HTTP 實現(xiàn) web 前端應用的服務器端推送的規(guī)范,它是屬于上面我們說到的 流信息 的一種。
以前沒有接觸過的童鞋,有興趣可以閱讀參考下相關的資料:
那么我們?nèi)绾斡?deno,實現(xiàn)一個 Server-sent events 接口呢?
可能有些不了解的童鞋要問了,什么是 deno?
不了解的童鞋可以去 deno 官網(wǎng)了解下,這里不做詳細的介紹。
Deno — A modern runtime for JavaScript and TypeScript
實現(xiàn) http server
首先,我們得了解下,在 deno 中,如何用標準庫實現(xiàn)一個 http server。
最簡單的實現(xiàn)方式,如下面的示例所示:
// 從標準庫中導入 serve 實例
import { serve } from "https://deno.land/std@0.175.0/http/server.ts";
// 請求 http 請求的處理器
function handler(_req: Request): Response {
return new Response("Hello, World!");
}
console.log("Listening on http://localhost:8000");
// 啟動服務
serve(handler);
實現(xiàn)流模式的響應
deno 官方的示例中,有一個示例展示如何發(fā)送流數(shù)據(jù)。
HTTP Server: Streaming - Deno by Example
我們結(jié)合 Server-sent events 的要求,
我們可以改造一下該示例,改造后的代碼如下所示:
import { Server } from "https://deno.land/std@0.175.0/http/server.ts";
const encoder = new TextEncoder();
// 請求 http 請求的處理器
function handler(_req: Request): Response {
let timer: number | undefined = undefined;
const body = new ReadableStream({
start(controller) {
// 增加定時器,定時朝流中推送文本數(shù)據(jù)
timer = setInterval(() => {
// 文本數(shù)據(jù),遵循一定的格式標準
const message = `event: message\ndata: It is ${new Date().toISOString()}\n\n`;
controller.enqueue(encoder.encode(message));
}, 1000);
},
cancel() {
if (timer !== undefined) {
clearInterval(timer);
}
},
});
return new Response(body, {
// 設置專用響應頭,使 Server-sent events 數(shù)據(jù)能夠正常的傳輸
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
"Transfer-Encoding": "chunked",
},
});
}
const server = new Server({ port: 8082, handler: handler });
await server.listenAndServe();
在我們的前端頁面里,可以直接通過下面的方式,調(diào)用該接口:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>sse</title>
</head>
<body>
<div>test</div>
<script>
const source = new EventSource("http://host:port", { withCredentials: false });
source.onopen = function () {
console.log("EventSource 連接成功");
};
source.onmessage = function (event) {
try {
if (event.data && typeof event.data === "string") {
console.log(event.data)
}
} catch (error) {
console.log("EventSource 結(jié)束消息異常", error);
}
};
// 監(jiān)聽錯誤
source.onerror = function (event) {
// handle error
console.log(event);
};
</script>
</body>
</html>