記錄--服務端推送到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
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...