https://juejin.cn/post/7325730345840066612
前言
在日常的開發(fā)中,我們經(jīng)常能碰見服務(wù)端需要主動(dòng)推送給客戶端數(shù)據(jù)的業(yè)務(wù)場景,比如數(shù)據(jù)大屏的實(shí)時(shí)數(shù)據(jù),比如消息中心的未讀消息,比如聊天功能等等。
本文主要介紹SSE的使用場景和如何使用SSE。
服務(wù)端向客戶端推送數(shù)據(jù)的實(shí)現(xiàn)方案有哪幾種?
我們常規(guī)實(shí)現(xiàn)這些需求的方案有以下三種
輪詢簡介
在很久很久以前,前端一般使用輪詢來進(jìn)行服務(wù)端向客戶端進(jìn)行消息的偽推送,為什么說輪詢是偽推送?因?yàn)檩喸儽举|(zhì)上還是通過客戶端向服務(wù)端發(fā)起一個(gè)單項(xiàng)傳輸?shù)恼埱螅?wù)端對這個(gè)請求做出響應(yīng)而已。通過不斷的請求來實(shí)現(xiàn)服務(wù)端向客戶端推送數(shù)據(jù)的錯(cuò)覺。并不是服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。顯然,輪詢一定是上述三個(gè)方法里最下策的決定。
- 首先輪詢需要不斷的發(fā)起請求,每一個(gè)請求都需要經(jīng)過http建立連接的流程(比如三次握手,四次揮手),是沒有必要的消耗。
- 客戶端需要從頁面被打開的那一刻開始就一直處理請求。雖然每次輪詢的消耗不大,但是一直處理請求對于客戶端來說一定是不友好的。
- 瀏覽器請求并發(fā)是有限制的。比如Chrome 最大并發(fā)請求數(shù)目為 6,這個(gè)限制還有一個(gè)前提是針對同一域名的,超過這一限制的后續(xù)請求將會(huì)被阻塞。而輪詢意味著會(huì)有一個(gè)請求長時(shí)間的占用并發(fā)名額。
- 而如果輪詢時(shí)間較長,可能又沒有辦法非常及時(shí)的獲取數(shù)據(jù)
websocket簡介
websocket是一個(gè)雙向通訊的協(xié)議,他的優(yōu)點(diǎn)是,可以同時(shí)支持客戶端和服務(wù)端彼此相互進(jìn)行通訊。功能上很強(qiáng)大。
缺點(diǎn)也很明顯,websocket是一個(gè)新的協(xié)議,ws/wss。也就是說,支持http協(xié)議的瀏覽器不一定支持ws協(xié)議。相較于SSE來說,websocket因?yàn)楣δ芨鼜?qiáng)大。結(jié)構(gòu)更復(fù)雜。所以相對比較重。
SSE簡介
sse是一個(gè)單向通訊的協(xié)議也是一個(gè)長鏈接,它只能支持服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),但是無法讓客戶端向服務(wù)端推送消息。
長鏈接是一種HTTP/1.1的持久連接技術(shù),它允許客戶端和服務(wù)器在一次TCP連接上進(jìn)行多個(gè)HTTP請求和響應(yīng),而不必為每個(gè)請求/響應(yīng)建立和斷開一個(gè)新的連接。長連接有助于減少服務(wù)器的負(fù)載和提高性能。
SSE的優(yōu)點(diǎn)是,它是一個(gè)輕量級的協(xié)議,相對于websockte來說,他的復(fù)雜度就沒有那么高,相對于客戶端的消耗也比較少。而且SSE使用的是http協(xié)議(websocket使用的是ws協(xié)議),也就是現(xiàn)有的服務(wù)端都支持SSE,無需像websocket一樣需要服務(wù)端提供額外的支持。注意哦,上圖是SSE對于瀏覽器的兼容不是對于服務(wù)端的兼容。
websocket和SSE有什么區(qū)別?
輪詢
對于當(dāng)前計(jì)算機(jī)的發(fā)展來說,幾乎很少出現(xiàn)同時(shí)不支持websocket和sse的情況,所以輪詢是在極端情況下瀏覽器實(shí)在是不支持websocket和see的下策。
Websocket和SSE
我們一般的服務(wù)端和客戶端的通訊基本上使用這兩個(gè)方案。首先聲明:這兩個(gè)方案沒有絕對的好壞,只有在不同的業(yè)務(wù)場景下更好的選擇。SSE的官方對于SSE和Websocket的評價(jià)是
- WebSocket是全雙工通道,可以雙向通信,功能更強(qiáng);SSE是單向通道,只能服務(wù)器向?yàn)g覽器端發(fā)送。
- WebSocket是一個(gè)新的協(xié)議,需要服務(wù)器端支持;SSE則是部署在HTTP協(xié)議之上的,現(xiàn)有的服務(wù)器軟件都支持。
- SSE是一個(gè)輕量級協(xié)議,相對簡單;WebSocket是一種較重的協(xié)議,相對復(fù)雜。
- SSE默認(rèn)支持?jǐn)嗑€重連,WebSocket則需要額外部署。
- SSE支持自定義發(fā)送的數(shù)據(jù)類型。
Websocket和SSE分別適用于什么業(yè)務(wù)場景?
對于SSE來說,它的優(yōu)點(diǎn)就是輕,而且對于服務(wù)端的支持度要更好。換言之,可以使用SSE完成的功能需求,沒有必要使用更重更復(fù)雜的websocket。比如:數(shù)據(jù)大屏的實(shí)時(shí)數(shù)據(jù),消息中心的消息推送等一系列只需要服務(wù)端單方面推送而不需要客戶端同時(shí)進(jìn)行反饋的需求,SSE就是不二之選。對于Websocket來說,他的優(yōu)點(diǎn)就是可以同時(shí)支持客戶端和服務(wù)端的雙向通訊。所適用的業(yè)務(wù)場景:最典型的就是聊天功能。這種服務(wù)端需要主動(dòng)向客戶端推送信息,并且客戶端也有向服務(wù)端推送消息的需求時(shí),Websocket就是更好的選擇。
SSE有哪些主要的API?
建立一個(gè)SSE鏈接 :var source = new EventSource(url);
SSE連接狀態(tài)
- 0,相當(dāng)于常量EventSource.CONNECTING,表示連接還未建立,或者連接斷線。
- 1,相當(dāng)于常量EventSource.OPEN,表示連接已經(jīng)建立,可以接受數(shù)據(jù)。
- 2,相當(dāng)于常量EventSource.CLOSED,表示連接已斷,且不會(huì)重連。
SSE相關(guān)事件
- open事件(連接一旦建立,就會(huì)觸發(fā)open事件,可以定義相應(yīng)的回調(diào)函數(shù))
- message事件(收到數(shù)據(jù)就會(huì)觸發(fā)message事件)
- error事件(如果發(fā)生通信錯(cuò)誤(比如連接中斷),就會(huì)觸發(fā)error事件)
數(shù)據(jù)格式
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
SSE相關(guān)文檔:.w3cschool.cn/nwfchn/wpi3cozt.html顯然,如果直接看api介紹不論是看這里還是看官網(wǎng),大部分同學(xué)都是比較懵圈的狀態(tài),那么我們寫個(gè)demo來看一下?
如何實(shí)操一個(gè)SSE鏈接?Demo↓
這里Demo前端使用的就是最基本的html靜態(tài)頁面連接,沒有使用任何框架。后端選用語言是node,框架是Express。理論上,把這兩段端代碼復(fù)制過去跑起來就直接可以用了。
- 第一步,建立一個(gè)index.html文件,然后復(fù)制前端代碼Demo到index.html文件中,打開文件
- 第二步,進(jìn)入一個(gè)新的文件夾,建立一個(gè)index.js文件,然后將后端Demo代碼復(fù)制進(jìn)去,然后在該文件夾下執(zhí)行
npm init
npm i express
node index
前端代碼Demo
<!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>Document</title>
</head>
<body>
<ul id="ul">
</ul>
</body>
<script>
function createLi(data){
let li = document.createElement("li");
li.innerHTML = String(data.message);
return li;
}
let source = ''
if (!!window.EventSource) {
source = new EventSource('http://localhost:8088/sse/');
}else{
throw new Error("當(dāng)前瀏覽器不支持SSE")
}
source.onopen = function(event) {
console.log(source.readyState);
console.log("長連接打開");
};
source.onmessage = function(event) {
console.log(JSON.parse(event.data));
console.log("收到長連接信息");
let li = createLi(JSON.parse(event.data));
document.getElementById("ul").appendChild(li)
};
source.onerror = function(event) {
console.log(source.readyState);
console.log("長連接中斷");
};
</script>
</html>
后端代碼Demo(node的express)
const express = require('express');
const app = express();
const port = 8088;
app.all("*", function(req, res, next) {
res.header("Access-Control-Allow-Origin", '*');
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header("Access-Control-Allow-Credentials", true);
if (req.method == 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
})
app.get("/sse",(req,res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
console.log("進(jìn)入到長連接了")
setInterval(() => {
console.log("正在持續(xù)返回?cái)?shù)據(jù)中ing")
const data = {
message: `Current time is ${new Date().toLocaleTimeString()}`
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
})
app.listen(port, () => {
console.log(`項(xiàng)目啟動(dòng)成功-http://localhost:${port}`)
})
效果
總結(jié)
- websocket是一個(gè)新的協(xié)議,ws/wss協(xié)議
- 如果只需要服務(wù)端向客戶端推送消息,推薦使用SSE
- 如果需要服務(wù)端和客戶端雙向推送,請選擇websocket
- 不論是SSE還是websocket,對于瀏覽器的兼容性都不錯(cuò)
- 輪詢是下策,很占用客戶端資源,不建議使用。(不過偷懶的時(shí)候他確實(shí)方便)
該文章在 2025/4/22 17:21:43 編輯過