當前位置: 妍妍網 > 碼農

如果讓你實作即時訊息推播你會用什麽技術?輪詢、websocket還是sse

2024-03-07碼農

前言

大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心~

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

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

image.png

伺服端向客戶端推播數據的實作方案有哪幾種?

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

  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

  • 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 //長連結標識

    image.png

    SSE:相關文件,文件入口文件入口文件入口文件入口 [1]

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

    image.png

    demo請看下方

    我更建議您先把Demo跑起來,然後在看看上面這個 w3cschool的SSE文件 [2] 。兩個配合一起看,會更方便理解些。

    image.png

    如何實操一個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 //啟動服務

    image.png

    在這一層資料夾下執行命令。

    完成以上操作就可以把計畫跑起來了

    前端程式碼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 ${newDate().toLocaleTimeString()}`
    };
    res.write(`data: ${JSON.stringify(data)}\n\n`);
    }, 1000);
    })
    //建立計畫
    app.listen(port, () => {
    console.log(`計畫啟動成功-http://localhost:${port}`)
    })


    效果


    結語

    我是林三心

  • 一個待過 小型toG型外包公司、大型外包公司、小公司、潛力型創業公司、大公司 的作死型前端選手;

  • 一個偏前端的全幹工程師;

  • 一個不正經的金塊作者;

  • 逗比的B站up主;

  • 不帥的小紅書博主;

  • 喜歡打鐵的籃球菜鳥;

  • 喜歡歷史的乏味少年;

  • 喜歡rap的五音不全弱雞如果你想一起學習前端,一起摸魚,一起研究簡歷最佳化,一起研究面試進步,一起交流歷史音樂籃球rap,可以來俺的摸魚學習群哈哈,點這個,有7000多名前端小夥伴在等著一起學習哦 -->

  • 廣州的兄弟可以約飯哦,或者約球~我負責打鐵,你負責進球,謝謝~