這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 在日常的開發中,我們經常能碰見服務端需要主動推送給客戶端數據的業務場景,比如數據大屏的實時數據,比如消息中心的未讀消息,比如聊天功能等等。 本文主要介紹SSE的使用場景和如何使用SSE。 服務端向客戶端推送數據的實現方案有哪幾種? ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
在日常的開發中,我們經常能碰見服務端需要主動推送給客戶端數據的業務場景,比如數據大屏的實時數據,比如消息中心的未讀消息,比如聊天功能等等。
本文主要介紹SSE的使用場景和如何使用SSE。
服務端向客戶端推送數據的實現方案有哪幾種?
我們常規實現這些需求的方案有以下三種
- 輪詢
- websocket
- SSE
輪詢簡介
在很久很久以前,前端一般使用輪詢來進行服務端向客戶端進行消息的偽推送,為什麼說輪詢是偽推送?因為輪詢本質上還是通過客戶端向服務端發起一個單項傳輸的請求,服務端對這個請求做出響應而已。通過不斷的請求來實現服務端向客戶端推送數據的錯覺。並不是服務端主動向客戶端推送數據。顯然,輪詢一定是上述三個方法里最下策的決定。
輪詢的缺點:
- 首先輪詢需要不斷的發起請求,每一個請求都需要經過http建立連接的流程(比如三次握手,四次揮手),是沒有必要的消耗。
- 客戶端需要從頁面被打開的那一刻開始就一直處理請求。雖然每次輪詢的消耗不大,但是一直處理請求對於客戶端來說一定是不友好的。
- 瀏覽器請求併發是有限制的。比如Chrome 最大併發請求數目為 6,這個限制還有一個前提是針對同一功能變數名稱的,超過這一限制的後續請求將會被阻塞。而輪詢意味著會有一個請求長時間的占用併發名額。
- 而如果輪詢時間較長,可能又沒有辦法非常及時的獲取數據
websocket簡介
websocket是一個雙向通訊的協議,他的優點是,可以同時支持客戶端和服務端彼此相互進行通訊。功能上很強大。
缺點也很明顯,websocket是一個新的協議,ws/wss。也就是說,支持http協議的瀏覽器不一定支持ws協議。
相較於SSE來說,websocket因為功能更強大。結構更複雜。所以相對比較重。
websocket對於各大瀏覽器的相容性↓
SSE簡介
sse是一個單向通訊的協議也是一個長鏈接,它只能支持服務端主動向客戶端推送數據,但是無法讓客戶端向服務端推送消息。
長鏈接是一種HTTP/1.1的持久連接技術,它允許客戶端和伺服器在一次TCP連接上進行多個HTTP請求和響應,而不必為每個請求/響應建立和斷開一個新的連接。長連接有助於減少伺服器的負載和提高性能。
SSE的優點是,它是一個輕量級的協議,相對於websockte來說,他的複雜度就沒有那麼高,相對於客戶端的消耗也比較少。而且SSE使用的是http協議(websocket使用的是ws協議),也就是現有的服務端都支持SSE,無需像websocket一樣需要服務端提供額外的支持。
註意:IE大魔王不支持SSE
SSE對於各大瀏覽器的相容性↓
註意哦,上圖是SSE對於瀏覽器的相容不是對於服務端的相容。
websocket和SSE有什麼區別?
輪詢
對於當前電腦的發展來說,幾乎很少出現同時不支持websocket和sse的情況,所以輪詢是在極端情況下瀏覽器實在是不支持websocket和see的下策。
Websocket和SSE
我們一般的服務端和客戶端的通訊基本上使用這兩個方案。首先聲明:這兩個方案沒有絕對的好壞,只有在不同的業務場景下更好的選擇。
SSE的官方對於SSE和Websocket的評價是
- WebSocket是全雙工通道,可以雙向通信,功能更強;SSE是單向通道,只能伺服器向瀏覽器端發送。
- WebSocket是一個新的協議,需要伺服器端支持;SSE則是部署在HTTP協議之上的,現有的伺服器軟體都支持。
- SSE是一個輕量級協議,相對簡單;WebSocket是一種較重的協議,相對複雜。
- SSE預設支持斷線重連,WebSocket則需要額外部署。
- SSE支持自定義發送的數據類型。
Websocket和SSE分別適用於什麼業務場景?
對於SSE來說,它的優點就是輕,而且對於服務端的支持度要更好。換言之,可以使用SSE完成的功能需求,沒有必要使用更重更複雜的websocket。
比如:數據大屏的實時數據,消息中心的消息推送等一系列只需要服務端單方面推送而不需要客戶端同時進行反饋的需求,SSE就是不二之選。
對於Websocket來說,他的優點就是可以同時支持客戶端和服務端的雙向通訊。所適用的業務場景:最典型的就是聊天功能。這種服務端需要主動向客戶端推送信息,並且客戶端也有向服務端推送消息的需求時,Websocket就是更好的選擇。
SSE有哪些主要的API?
建立一個SSE鏈接 :var source = new EventSource(url);
SSE連接狀態
source.readyState
- 0,相當於常量EventSource.CONNECTING,表示連接還未建立,或者連接斷線。
- 1,相當於常量EventSource.OPEN,表示連接已經建立,可以接受數據。
- 2,相當於常量EventSource.CLOSED,表示連接已斷,且不會重連。
SSE相關事件
- open事件(連接一旦建立,就會觸發open事件,可以定義相應的回調函數)
- message事件(收到數據就會觸發message事件)
- error事件(如果發生通信錯誤(比如連接中斷),就會觸發error事件)
數據格式
Content-Type: text/event-stream //文本返回格式 Cache-Control: no-cache //不要緩存 Connection: keep-alive //長鏈接標識
顯然,如果直接看api介紹不論是看這裡還是看官網,大部分同學都是比較懵圈的狀態,那麼我們寫個demo來看一下?
demo請看下方
我更建議您先把Demo跑起來,然後在看看上面這個w3cschool的SSE文檔。兩個配合一起看,會更方便理解些。
如何實操一個SSE鏈接?Demo↓
這裡Demo前端使用的就是最基本的html靜態頁面連接,沒有使用任何框架。 後端選用語言是node,框架是Express。
理論上,把這兩段端代碼複製過去跑起來就直接可以用了。
- 第一步,建立一個index.html文件,然後複製前端代碼Demo到index.html文件中,打開文件
- 第二步,進入一個新的文件夾,建立一個index.js文件,然後將後端Demo代碼複製進去,然後在該文件夾下執行
npm init //初始化npm npm i express //下載node 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> //生成li元素 function createLi(data){ let li = document.createElement("li"); li.innerHTML = String(data.message); return li; } //判斷當前瀏覽器是否支持SSE let source = '' if (!!window.EventSource) { source = new EventSource('http://localhost:8088/sse/'); }else{ throw new Error("當前瀏覽器不支持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", '*'); //允許的header類型 res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); //跨域允許的請求方式 res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); // 可以帶cookies 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("進入到長連接了") //持續返回數據 setInterval(() => { console.log("正在持續返回數據中ing") const data = { message: `Current time is ${new Date().toLocaleTimeString()}` }; res.write(`data: ${JSON.stringify(data)}\n\n`); }, 1000); }) //創建項目 app.listen(port, () => { console.log(`項目啟動成功-http://localhost:${port}`) })