你還在使用 websocket 實現實時消息推送嗎?

前言

在日常的開發中,我們經常能碰見服務端需要主動推送給客戶端數據的業務場景,比如數據大屏的實時數據,比如消息中心的未讀消息,比如聊天功能等等。

本文主要介紹 SSE 的使用場景和如何使用 SSE。

服務端向客戶端推送數據的實現方案有哪幾種?

我們常規實現這些需求的方案有以下三種

  1. 輪詢

  2. websocket

  3. SSE

輪詢簡介

在很久很久以前,前端一般使用輪詢來進行服務端向客戶端進行消息的僞推送,爲什麼說輪詢是僞推送?因爲輪詢本質上還是通過客戶端向服務端發起一個單項傳輸的請求,服務端對這個請求做出響應而已。

通過不斷的請求來實現服務端向客戶端推送數據的錯覺。並不是服務端主動向客戶端推送數據。顯然,輪詢一定是上述三個方法裏最下策的決定。

輪詢的缺點:

  1. 首先輪詢需要不斷的發起請求,每一個請求都需要經過 http 建立連接的流程(比如三次握手,四次揮手),是沒有必要的消耗。

  2. 客戶端需要從頁面被打開的那一刻開始就一直處理請求。雖然每次輪詢的消耗不大,但是一直處理請求對於客戶端來說一定是不友好的。

  3. 瀏覽器請求併發是有限制的。比如 Chrome 最大併發請求數目爲 6,這個限制還有一個前提是針對同一域名的,超過這一限制的後續請求將會被阻塞。而輪詢意味着會有一個請求長時間的佔用併發名額。

  4. 而如果輪詢時間較長,可能又沒有辦法非常及時的獲取數據

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 的評價是

  1. WebSocket 是全雙工通道,可以雙向通信,功能更強;SSE 是單向通道,只能服務器向瀏覽器端發送。

  2. WebSocket 是一個新的協議,需要服務器端支持;SSE 則是部署在 HTTP 協議之上的,現有的服務器軟件都支持。

  3. SSE 是一個輕量級協議,相對簡單;WebSocket 是一種較重的協議,相對複雜。

  4. SSE 默認支持斷線重連,WebSocket 則需要額外部署。

  5. SSE 支持自定義發送的數據類型。

Websocket 和 SSE 分別適用於什麼業務場景?

對於 SSE 來說,它的優點就是輕,而且對於服務端的支持度要更好。換言之,可以使用 SSE 完成的功能需求,沒有必要使用更重更復雜的 websocket。

比如:數據大屏的實時數據,消息中心的消息推送等一系列只需要服務端單方面推送而不需要客戶端同時進行反饋的需求,SSE 就是不二之選。

對於 Websocket 來說,他的優點就是可以同時支持客戶端和服務端的雙向通訊。所適用的業務場景:最典型的就是聊天功能。這種服務端需要主動向客戶端推送信息,並且客戶端也有向服務端推送消息的需求時,Websocket 就是更好的選擇。

SSE 有哪些主要的 API?

建立一個SSE鏈接 :var source = new EventSource(url);

SSE 連接狀態

source.readyState

SSE 相關事件

數據格式

Content-Type: text/event-stream //文本返回格式
Cache-Control: no-cache  //不要緩存
Connection: keep-alive //長鏈接標識

SSE:相關文檔,https://www.w3cschool.cn/nwfchn/wpi3cozt.html

顯然,如果直接看 api 介紹不論是看這裏還是看官網,大部分同學都是比較懵圈的狀態,那麼我們寫個 demo 來看一下?

如何實操一個 SSE 鏈接?Demo↓

這裏 Demo 前端使用的就是最基本的 html 靜態頁面連接,沒有使用任何框架。後端選用語言是 node,框架是 Express。

理論上,把這兩段端代碼複製過去跑起來就直接可以用了。

  1. 第一步,建立一個 index.html 文件,然後複製前端代碼 Demo 到 index.html 文件中,打開文件

  2. 第二步,進入一個新的文件夾,建立一個 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}`)
})

效果

總結

  1. SSE 比 websocket 更輕

  2. SSE 是基於 http/https 協議的

  3. websocket 是一個新的協議,ws/wss 協議

  4. 如果只需要服務端向客戶端推送消息,推薦使用 SSE

  5. 如果需要服務端和客戶端雙向推送,請選擇 websocket

  6. 不論是 SSE 還是 websocket,對於瀏覽器的兼容性都不錯

  7. 輪詢是下策,很佔用客戶端資源,不建議使用。(不過偷懶的時候他確實方便)

  8. IE 不支持 SSE

  9. 小白同學 demo 如果跑不明白可以私信我

對了,小程序不支持 SSE 哦

作者:工邊頁字

來源:https://juejin.cn/post/7325730345840066612

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tn7W-uoFwDh7F2GniEqgOA