記錄--服務端推送到Web前端有哪幾種方式?

来源:https://www.cnblogs.com/smileZAZ/archive/2023/03/08/17194994.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 這個問題? 這個問題一般會出現在面試題裡面,然後回答一些諸如輪詢、WebSocket之類的答案。當然,實際開發中,也會遇到類似別人給你贊了,要通知給你的情況。這時服務端推送給Web前端(先局限在Web前端,畢竟其他端還有一些特殊方法)到底 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

這個問題?

這個問題一般會出現在面試題裡面,然後回答一些諸如輪詢、WebSocket之類的答案。當然,實際開發中,也會遇到類似別人給你贊了,要通知給你的情況。這時服務端推送給Web前端(先局限在Web前端,畢竟其他端還有一些特殊方法)到底有多少種方法?它們到底是怎麼實現的?

寫個Demo看看吧,這樣正好把主要(不清楚是否還有漏的)的方案都實現一遍。先看效果:

其中的代碼也上傳到GitHub了,在server-pushgithub.com/waiter/serv… )這裡。

各種方案

從上面的截圖也已經可以看出,本文主要寫了5種方案,那麼接下來也就一個一個簡單介紹一下吧。

另外,本文涉及的Demo,後端直接使用原生的Node.js開發,沒有使用KoaExpress之類的,也沒有使用額外的庫,類似socket.io,主要是想保持最精簡的狀態來呈現。前端也只是在最基礎的HTML上,引入了jQuery來方便做DOM操作,也引入了Bootstrap來快速實現統一的樣式,而未再引入類似VueReact之類的框架。

還有,為了觸發服務端推送,這邊在前端頁面上加了個輸入框和按鈕,來將消息發送給後端,後端會緩存消息,並觸發推送,後端大體代碼類似:

// 緩存需要推送的信息
const datas = [];
// 各種方案觸發推送時的回調
const callbacks = {};

// 註冊介面回調
server.on('request', (req, res) => {
    const { pathname, query } = parse(req.url, true);
    // 如果發現是前端觸發推送介面
    if (pathname === '/api/push') {
      if (query.info) {
        // 緩存推送信息
        datas.push(query.info);
        const d = JSON.stringify([query.info]);
        // 觸發所有推送回調
        Object.keys(callbacks).forEach(k => callbacks[k](d));
      }
      res.end('ok');
    }
});

1. 輪詢(短輪詢

這是最簡單直觀的方法,就是每隔一段時間發起一個請求到後端詢問是否有新信息。至於為什麼又叫短輪詢,其是相對於後續要說的長輪詢來對比的。

這樣前端只要設置一個setTimeout來定時請求就行:

// 緩存前端已經獲取的最新id
let id = 0;

function poll() {
  $.ajax({
    url: '/api/polling',
    data: { id },
    }).done(res => {
      id += res.length;
    }).always(() => {
      // 10s後再次請求
      setTimeout(poll, 10000);
    });
}

poll();

後端也是否簡單,根據前端給到的id,看看有沒有新消息,有就返回,沒有就返回空

const id = parseInt(query.id || '0', 10) || 0;
res.writeHead(200, { 'Content-Type': 'application/json;' });
res.end(JSON.stringify(datas.slice(id)));

這個看起來其實時性與請求頻率成正相關,但是當請求頻率上來了,性能浪費也就越高,畢竟可能大部分請求都是無意義的。

2. 長輪詢

在翻找資料的時候,發現有些資料會直接把這個當作短輪詢,有點匪夷所思。這裡的長輪詢相對前面的輪詢來說,算是一種優化。具體就是前端發起請求到後端,後端不直接返回,而是等待有新信息時再返回。所以這樣發起的一個請求,可能需要很長的時間才能等到返回,故而叫做長輪詢。

其前端代碼基本和短輪詢一致,只不過把請求的超時時間設置較長比如1分鐘),然後無論請求成功或失敗,馬上再次發起請求即可。

相對來說,後端的寫法就要稍微改動一下

const id = parseInt(query.id || '0', 10) || 0;
const cbk = 'long-polling';
delete callbacks[cbk];
const data = datas.slice(id);
res.writeHead(200, { 'Content-Type': 'application/json' });
// 發起請求時,正好有新消息就返回
if (data.length) {
  return res.end(JSON.stringify(data));
}
req.on('close', () => {
  delete callbacks[cbk];
});
// 註冊新消息回調
callbacks[cbk] = (d) => {
  res.end(d);
};

這樣,**相對於短輪詢,少了很多無意義的請求,而且消息的實時性也非常好。**不過,當服務端有異常時,會導致長輪詢短時間內不斷發起請求,可能讓服務端承受更大的壓力,所以兩次長輪詢之間最好有一定間隔,或者異常檢測機制。

3. SSE(Server-sent events

Traditionally, a web page has to send a request to the server to receive new data; that is, the page requests data from the server. With server-sent events, it's possible for a server to send new data to a web page at any time, by pushing messages to the web page. These incoming messages can be treated as Events + data inside the web page.

前面提到的輪詢、長輪詢都是一問一答式的,一次請求,無法推送多次消息到前端。而SSE就厲害了,一次請求,N次推送

其原理,或者說類比,個人認為可以理解為下載一個巨大的文件,文件的內容分塊傳給前端,每塊就是一次消息推送

聽起來很厲害,先看看後端代碼要怎麼寫

const cbk = 'sse';
delete callbacks[cbk];
res.writeHead(200, {
  // 這個是核心
  'Content-Type': 'text/event-stream',
  'Connection': 'keep-alive',
});
// 把緩存的信息推送給前端
res.write(`data: ${JSON.stringify(datas)}\n\n`);
// 註冊新消息回調
callbacks[cbk] = (d) => {
  res.write(`data: ${d}\n\n`);
};
req.on('close', () => {
  delete callbacks[cbk];
});

後端代碼很簡單,核心在於Content-Type: text/event-stream,這要讓前端知道這是SSE,還有就是傳輸信息的格式比較特別一點,詳細的可以看 MDNdeveloper.mozilla.org/en-US/docs/…

而前端有專門的EventSource來接收,使用起來很方便

const es = new EventSource('/api/sse');
es.onmessage = (e) => {
   try {
    const c = JSON.parse(e.data);
    } catch (err) {
      console.log(err);
    }
}

這樣就好了,如果你打開Chrome的開發者工具中的網路標簽,你就會發現Chrome對於SSE請求,有專門的展示標簽

另外,**SSE還支持自動重連!**伺服器短時間異常,恢復之後,無需額外代碼,SSE就自動重連上了。不過,本人在實際工作中卻沒有碰到過SSE,也就在面試題中見過。

4. WebSocket

既然有了SSE,那還要WebSocket幹啥啊?因為WebSocket可以一次連接,雙向推送,而SSE只能從服務端推送到前端。從這個角度來看,用WebSocket來單做服務端推送,有點大材小用了。

另外,初見WebSocket,可能會對其與Socket的聯繫有點疑惑。Socket協議是與HTTP協議平級的,而WebSocket協議是基於HTTP協議的,不過兩者在使用層面上是十分相近的。

其前端使用寫法與SSE類似,十分簡單,只不過請求鏈接為ws://或者wss://開頭(相當於http://https://

const ws = new WebSocket('ws://localhost:3000/ws');
ws.onmessage = e => {
  try {
    const c = JSON.parse(e.data);
    } catch (err) {
      console.log(err);
    }
};

而如果要用原生Node.js來寫WebSocket服務,就會麻煩一些了,一般情況都會使用類似socket.io之類的三方庫來降低實現成本。這邊也就在網上摘抄了一段代碼來簡單實現一下,詳細的可以看Github上的Demo代碼

server.on('upgrade', (req, socket) => {
  const cbk = 'ws';
  delete callbacks[cbk];
  const acceptKey = req.headers['sec-websocket-key']; 
  const hash = generateAcceptValue(acceptKey); 
  const responseHeaders = [ 'HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${hash}` ];
  // 告知前端這是WebSocket協議
  socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
  // 發送數據
  socket.write(constructReply(datas));
  callbacks[cbk] = (d) => {
    socket.write(constructReply(d));
    }
    socket.on('close', () => {
      delete callbacks[cbk];
    });
});

這個在Chrome瀏覽器中,也有專門的標簽頁展示

不過,它沒有像SSE一樣有自動重連,這塊需要自行實現。

一般網頁實時聊天之類需要雙向推送的,都會使用WebSocket來實現。

5. iFrame

這算是找資料的時候意外發現的,之前並不知道還有這樣的玩法。原理類似使用iFrame載入一個巨大的網頁,利用瀏覽器會一邊載入一邊解析執行返回的HTML,通過分次返回Script標簽來實現消息推送。其實現類似SSE,不過看起來就比較==hack==。

前端代碼很簡單,只不過要註冊一個回調給iframe使用

// 註冊給iframe使用的方法
window.change = function(data) {

};
$('body').append('<iframe src="/api/iframe"></iframe>');

而後端也很簡單,有消息的時候返回script標簽即可

const cbk = 'iframe';
delete callbacks[cbk];
// 返回緩存信息
res.write(`<script>window.parent.change(${JSON.stringify(datas)});</script>`);
callbacks[cbk] = (d) => {
  res.write(`<script>window.parent.change(${d});</script>`);
};
req.on('close', () => {
  delete callbacks[cbk];
});

相當奇淫巧技了。不過,似乎沒找到怎麼判斷載入異常的情況,可能需要自行加心跳來實現了

另外,很多文章在說使用iFrame方法時,會導致瀏覽器顯示未載入完,圖標一直轉的樣子。但是個人認為,圖標一直轉是因為頁面一直沒有onload,那麼在頁面onload之後,再創建iFrame就應該沒有這個問題了

總結一下

上面實現了5種推送的方案,弄了一個表格簡單對比一下

方案)實時單次連接自動重連斷線檢測雙向推送無跨域
短輪詢
長輪詢
SSE
WebSocket
iFrame

本文轉載於:

https://juejin.cn/post/7113813187727720461

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、Redis簡介 Redis全稱為 Remote Dictionary Server(遠程詞典服務),開源(BSD許可)的,使用 C 語言開發的。 Redis是記憶體中的數據結構存儲系統,它可以用作資料庫、緩存和消息中間件。 支持多種類型的數據結構,如 字元串(strings), 散列(hashes ...
  • MySQL查看資料庫性能常用命令 # 列出MySQL伺服器運行各種狀態值 show global status; # 查詢MySQL伺服器配置信息語句 show variables; # 慢查詢 show variables like '%slow%'; # MySQL伺服器最大連接數 show v ...
  • 摘要:Bucket存儲是數據共用中重要的一環,當前階段,bucket存儲可以將列存中的CU數據和DN節點解綁。 本文分享自華為雲社區《存算分離之bucket表——【玩轉PB級數倉GaussDB(DWS)】》,作者:yd_278301229 。 在雲原生環境,用戶可以自由配置cup型號、記憶體、磁碟、帶 ...
  • 《高性能MySQL》第四版發佈後,收到了很多讀者的反饋,其中關註最多的是作為一個初學者,應該如何能夠較為系統的學習MySQL,從而應對日常工作或者獲得更好的職業發展。於是和多個業內朋友討論後,整理了一些MySQL學習的推薦資源,供初學者參考。 本文分成幾部分,包括業界專家的經驗、推薦書籍、視頻/音頻 ...
  • 摘要: Gaussdb的HA採用主備從的架構實現數據可靠性。當主DN發生故障時,備DN走failover流程,升級成為新主DN,保證集群不因單DN故障而中斷業務。 本文分享自華為雲社區《【玩轉PB級數倉GaussDB(DWS)】dws高可用之failover流程大解密》,作者:fxy0224。 眾所 ...
  • 本文將從以下五部分切入,講述日誌系統的演進之路:攜程日誌的背景和現狀、如何搭建一套日誌系統、從 ElasticSearch 到 Clickhouse 存儲演進、日誌3.0重構及未來計劃。 ...
  • 目錄結構 Unity工程指UnityLibrary目錄下文件; 安卓工程指app目錄下文件; 整體指App目錄下不包括app和UnityLibrary; 1.Unity打包時勾選導出安卓工程; 拷貝gradle.properties中:unityStreamingAssets=xxx 到整體工程的g ...
  • 騰訊雲應用安全已在加固過程中刪除簽名信息,加固後的安裝包需要重新簽名。同樣近期360加固助手簽名設置也需要購買高級加固服務。在進行加固後我們需要手動簽名cmd 手動簽名 apksigner 1、檢查簽名文件*.jks或者*.keystore keytool -list -v -keystore 簽名 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...