什麼是WebSockets、伺服器傳送事件、長輪詢、WebRTC、WebTransport?

banq發表於2024-03-20

對於現代實時網路應用程式來說,從伺服器向客戶端傳送事件的能力是不可或缺的。多年來,人們根據這種需要開發了多種方法,每種方法都有自己的優點和缺點。

  • 最初,長輪詢是唯一可用的方法。
  • 隨後,WebSockets 取而代之,為雙向通訊提供了更強大的解決方案。
  • 繼 WebSockets 之後,伺服器傳送事件(SSE)為伺服器到客戶端的單向通訊提供了一種更簡單的方法。
  • 展望未來,WebTransport 協議有望透過提供更高效、靈活和可擴充套件的方法,進一步徹底改變這一局面。
  • 對於小眾用例,WebRTC 也可用於伺服器-客戶端事件。

本文旨在深入探討這些技術,比較它們的效能,強調它們的優勢和侷限性,並針對各種用例提出建議,幫助開發人員在構建實時網路應用時做出明智的決定。

什麼是長輪詢?
長輪詢是第一種 "駭客 "技術,可在瀏覽器中透過 HTTP 啟用伺服器-客戶端訊息傳遞方法。

該技術透過正常的 XHR 請求模擬伺服器推送通訊。與傳統的輪詢(客戶端以固定的時間間隔反覆向伺服器請求資料)不同,長輪詢建立了一個與伺服器的連線,該連線一直保持開啟狀態,直到有新資料可用。一旦伺服器獲得新資訊,就會向客戶端傳送響應,然後關閉連線。收到伺服器的響應後,客戶端會立即發起新的請求,整個過程重複進行。

這種方法可以更即時地更新資料,減少不必要的網路流量和伺服器負載。不過,這種方法仍然會造成通訊延遲,而且效率低於 WebSockets 等其他實時技術。

<font>// long-polling in a JavaScript client<i>
function longPoll() {
    fetch('http:
//example.com/poll')<i>
        .then(response => response.json())
        .then(data => {
            console.log(
"Received data:", data);
            longPoll();
// Immediately establish a new long polling request<i>
        })
        .catch(error => {
           
/**
             * 在正常情況下,當連線超時或客戶端離線時,可能會出現錯誤。
             * 出現錯誤時,我們會在延遲一段時間後重新開始輪詢。
             */
<i>
            setTimeout(longPoll, 10000);
        });
}
longPoll();
// Initiate the long polling<i>

在客戶端實施長時間輪詢非常簡單,如上面的程式碼所示。但在後端,要確保客戶端接收到所有事件,並且在客戶端正在重新連線時不會錯過更新,可能會遇到很多困難。

什麼是 WebSockets?
WebSockets 透過客戶端和伺服器之間的單個長期連線提供全雙工通訊通道。這項技術使瀏覽器和伺服器能夠交換資料,而無需 HTTP 請求-響應週期的開銷,從而為即時聊天、遊戲或金融交易平臺等應用的實時資料傳輸提供了便利。WebSockets 允許雙方在建立連線後獨立傳送資料,是傳統 HTTP 的一大進步,非常適合需要低延遲和高頻率更新的應用場景。

<font>// WebSocket in a JavaScript client<i>
const socket = new WebSocket('ws:
//example.com');<i>

socket.onopen = function(event) {
  console.log('Connection established');
 
// Sending a message to the server<i>
  socket.send('Hello Server!');
};

socket.onmessage = function(event) {
  console.log('Message from server:', event.data);
};

雖然 WebSocket API 的基本原理很容易使用,但在生產中卻顯得相當複雜。
套接字可能會失去連線,因此必須相應地重新建立。尤其是檢測連線是否仍然可用,可能非常棘手。
大多數情況下,你需要新增一個 "乒乓心跳 "來確保開啟的連線沒有關閉。
這種複雜性正是大多數人在 WebSockets 上使用 Socket.IO 等庫的原因,這些庫可以處理所有這些情況,甚至在需要時提供長時間輪詢的後備功能。

什麼是伺服器傳送事件?
伺服器傳送事件(SSE)提供了一種透過 HTTP 向客戶端推送伺服器更新的標準方式。與 WebSockets 不同,SSE 專為伺服器到客戶端的單向通訊而設計,因此非常適合實時新聞、體育比賽比分或客戶端需要實時更新而無需向伺服器傳送資料的任何情況。

你可以把伺服器-傳送-事件看作是一個單一的 HTTP 請求,在這個請求中,後端不會一次性傳送整個正文,而是保持連線暢通,並在每次需要向客戶端傳送事件時,透過傳送一行資料來涓滴回覆。

使用 SSE 建立用於接收事件的連線非常簡單。在瀏覽器的客戶端,使用生成事件的伺服器端指令碼的 URL 初始化一個 EventSource 例項。

監聽訊息需要將事件處理程式直接附加到 EventSource 例項。應用程式介面區分了通用訊息事件和命名事件,從而實現了更有條理的通訊。以下是如何在 JavaScript 中進行設定:

<font>// Connecting to the server-side event stream<i>
const evtSource = new EventSource(
"https://example.com/events");

// Handling generic message events<i>
evtSource.onmessage = event => {
    console.log('got message: ' + event.data);
};

與 WebSockets 不同的是,EventSource 會在連線丟失時自動重新連線。

在伺服器端,您的指令碼必須將 Content-Type 標頭設定為文字/事件流,並根據 SSE 規範格式化每條資訊。這包括指定事件型別、資料有效載荷以及事件 ID 和重試時間等可選欄位。

下面是如何在 Node.js Express 應用程式中設定一個簡單的 SSE 端點:

import express from 'express';
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/events', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
    });

    const sendEvent = (data) => {
        <font>// all message lines must be prefixed with 'data: '<i>
        const formattedData = `data: ${JSON.stringify(data)}\n\n`;
        res.write(formattedData);
    };

   
// Send an event every 2 seconds<i>
    const intervalId = setInterval(() => {
        const message = {
            time: new Date().toTimeString(),
            message: 'Hello from the server!',
        };
        sendEvent(message);
    }, 2000);

   
// Clean up when the connection is closed<i>
    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});
app.listen(PORT, () => console.log(`Server running on http:
//localhost:${PORT}`));<i>

什麼是 WebTransport API?
WebTransport 是一種先進的 API,旨在實現網路客戶端和伺服器之間高效、低延遲的通訊。它利用 HTTP/3 QUIC 協議實現了多種資料傳輸功能,如透過多個流以可靠或不可靠的方式傳送資料,甚至允許不按順序傳送資料。

這使得 WebTransport 成為需要高效能網路的應用(如實時遊戲、實時流媒體和協作平臺)的強大工具。

不過,值得注意的是,WebTransport 目前只是一個工作草案,尚未得到廣泛採用。截至目前(2024 年 3 月),WebTransport 仍處於工作草案階段,尚未得到廣泛支援。

您還不能在 Safari 瀏覽器中使用 WebTransport,Node.js 中也沒有本機支援。這限制了它在不同平臺和環境中的可用性。

即使 WebTransport 將得到廣泛支援,其 API 的使用也非常複雜,人們很可能會在 WebTransport 的基礎上構建庫,而不是直接在應用程式的原始碼中使用它。

什麼是 WebRTC?
WebRTC(Web Real-Time Communication:網路實時通訊)是一個開源專案和 API 標準,可直接在網路瀏覽器和移動應用程式中實現實時通訊(RTC)功能,而無需複雜的伺服器基礎設施或安裝額外的外掛。它支援點對點連線,可在瀏覽器之間進行流式音訊、影片和資料交換。WebRTC 設計用於穿越 NAT 和防火牆,利用 ICE、STUN 和 TURN 等協議在對等方之間建立連線。

雖然 WebRTC 可用於客戶端與客戶端之間的互動,但它也可用於伺服器與客戶端之間的通訊,即伺服器只是模擬客戶端。這種方法只適用於小範圍的使用案例,因此在下文中,WebRTC 將被忽略。

問題在於,要讓 WebRTC 起作用,無論如何都需要一個信令伺服器,然後再透過 websockets、SSE 或 WebTransport 執行。這就違背了使用 WebRTC 替代這些技術的初衷。

侷限性
1、雙向傳送資料
只有 WebSockets 和 WebTransport 允許雙向傳送資料,因此您可以透過同一連線接收伺服器資料和傳送客戶端資料。

雖然理論上長輪詢(Long-Polling)也可以做到這一點,但並不推薦這樣做,因為向現有的長輪詢連線傳送 "新 "資料無論如何都需要進行額外的 http 請求。因此,你可以在不中斷長輪詢連線的情況下,透過額外的 http 請求直接從客戶端向伺服器傳送資料。

伺服器傳送事件(Server-Sent-Events)不支援向伺服器傳送任何附加資料。你只能執行初始請求,而且即使在初始請求中,你也不能使用本地事件源 API 在 http-body 中傳送類似於 POST 的資料。取而代之的是,你必須將所有資料放在 url 引數中,這被認為是一種不利於安全的做法,因為憑據可能會洩露到伺服器日誌、代理和快取中。

每個域名 6 個請求的限制
大多數現代瀏覽器允許每個域有六個連線,這限制了所有穩定的伺服器到客戶端訊息傳遞方法的可用性。6 個連線的限制甚至可以在瀏覽器標籤頁中共享,因此當您在多個標籤頁中開啟同一個頁面時,它們必須相互共享 6 個連線池。這一限制是 HTTP/1.1-RFC 的一部分(它甚至定義了更低的連線數,即只有兩個連線)。

雖然這一策略可以防止網站所有者利用其訪問者對其他網站進行 D-DOS 操作,但當合法使用情況下需要多個連線來處理伺服器-客戶端通訊時,這就會成為一個大問題。為了解決這一限制,您必須使用 HTTP/2 或 HTTP/3,瀏覽器將只為每個域開啟一個連線,然後使用多路複用技術透過單個連線執行所有資料。雖然這樣可以提供幾乎無限量的並行連線,但 SETTINGS_MAX_CONCURRENT_STREAMS 設定會限制實際連線數。大多數配置的預設併發流數為 100。

理論上,瀏覽器也可以增加連線數限制,至少對於特定的 API(如 EventSource),但這些問題已被 chromium 和 Firefox 標記為 "不會修復"。

移動
在 Android 和 iOS 等作業系統上執行的移動應用程式中,維護開放連線(例如用於 WebSocket 等的連線)帶來了重大挑戰。移動作業系統旨在在一段時間不活動後自動將應用程式移至後臺,從而有效地關閉任何開啟的連線。此行為是作業系統資源管理策略的一部分,旨在節省電池並最佳化效能。因此,開發人員通常依賴移動推送通知作為將資料從伺服器傳送到客戶端的有效且可靠的方法。推送通知允許伺服器嚮應用程式發出新資料的警報,提示操作或更新,而無需持續開啟連線。

效能比較
比較 WebSocket、伺服器傳送事件 (SSE)、長輪詢和 WebTransport 的效能直接涉及評估各種條件下的關鍵方面,例如延遲、吞吐量、伺服器負載和可擴充套件性。

延遲:

  • WebSockets:由於其透過單個持久連線進行全雙工通訊,因此提供最低的延遲。非常適合即時資料交換至關重要的實時應用程式。
  • 伺服器傳送的事件:還為伺服器到客戶端的通訊提供低延遲,但如果沒有額外的 HTTP 請求,則無法本機將訊息傳送回伺服器。
  • 長輪詢:由於每次資料傳輸都依賴於建立新的 HTTP 連線,因此會產生較高的延遲,從而降低實時更新的效率。當客戶端仍在開啟新連線的過程中時,伺服器也可能想要傳送事件。在這些情況下,延遲會明顯變大。
  • WebTransport:承諾提供類似於 WebSocket 的低延遲,並具有利用 HTTP/3 協議實現更高效的多路複用和擁塞控制的額外優勢。

吞吐量:

  • WebSockets但吞吐量可能會受到反向壓力的影響,即客戶端處理資料的速度趕不上伺服器傳送資料的速度。
  • 伺服器傳送事件:可高效地向許多客戶端廣播資訊,開銷比 WebSockets 少,從而可能提高伺服器到客戶端單向通訊的吞吐量。
  • 長時間輪詢:由於頻繁開啟和關閉連線會消耗更多伺服器資源,因此吞吐量一般較低。
  • WebTransport:預計可在單個連線內支援單向和雙向流的高吞吐量,在需要多個流的情況下效能優於 WebSockets。

可擴充套件性和伺服器負載

  • WebSockets維持大量 WebSocket 連線會大大增加伺服器負載,可能會影響擁有眾多使用者的應用程式的可擴充套件性。
  • 伺服器傳送的事件:與 WebSockets 相比,它使用的是 "正常的 "HTTP 請求,而無需像 WebSockets 那樣執行協議更新,因此使用的連線開銷更少。
  • 長輪詢:由於頻繁建立連線會產生較高的伺服器負載,因此它的擴充套件性最差,只能作為一種備用機制。
  • WebTransport利用 HTTP/3 在處理連線和流方面的高效性,設計為高度可擴充套件,與 WebSockets 和 SSE 相比,可減少伺服器負載。

建議和用例適用性
在伺服器-客戶端通訊技術領域,每種技術都有其獨特的優勢和適用性。伺服器傳送事件(SSE)是最直接的實施方案,它利用與傳統網路請求相同的 HTTP/S 協議,從而規避了企業防火牆限制和其他協議可能出現的技術問題。它們很容易整合到 Node.js 和其他伺服器框架中,因此是需要伺服器到客戶端頻繁更新的應用程式的理想選擇,例如新聞提要、股票行情和實時事件流。

另一方面,WebSockets 在需要持續雙向通訊的場景中表現出色。WebSockets 支援持續互動的能力使其成為瀏覽器遊戲、聊天應用和實時體育更新的首選。

然而,儘管 WebTransport 潛力巨大,但在應用方面卻面臨挑戰。它沒有得到包括 Node.js 在內的伺服器框架的廣泛支援,也缺乏與 safari 的相容性。此外,WebTransport 對 HTTP/3 的依賴進一步限制了它的直接適用性,因為許多 WebServers(如 nginx)僅支援試驗性 HTTP/3。雖然 WebTransport 支援可靠和不可靠資料傳輸,在未來的應用中大有可為,但對於大多數用例來說,它還不是一個可行的選擇。

長輪詢Long-Polling 曾是一種常用技術,但由於其效率低下,而且重複建立新 HTTP 連線的開銷很大,現在已基本過時。雖然在不支援 WebSockets 或 SSE 的環境中,它可以作為一種備用技術,但由於其效能受到很大限制,一般不建議使用。

已知問題
所有實時流媒體技術都存在已知問題。當你在它們的基礎上構建任何東西時,請記住這些問題。

客戶端重新連線時可能會錯過事件
當客戶端正在連線、重新連線或離線時,可能會錯過伺服器上發生但無法流式傳輸到客戶端的事件。當伺服器每次都在流式傳輸完整內容(如實時更新的股票行情)時,這種漏掉的事件並不重要。但當後端要對部分結果進行流式處理時,就必須考慮漏報事件。如果在後端解決這個問題,規模會非常大,因為後端必須記住每個客戶端已經成功傳送了哪些事件。相反,這應該透過客戶端邏輯來實現。


公司防火牆可能導致問題
在使用任何流媒體技術時,公司基礎設施都會出現許多已知的問題。代理和防火牆可能會阻止流量或無意中破壞請求和響應。無論何時在這樣的基礎設施中實施實時應用程式,請確保首先測試技術本身是否適合您。
 

相關文章