首頁  >  文章  >  web前端  >  即時 Web 通訊:長/短輪詢、WebSockets 和 SSE 解釋 + Next.js 程式碼

即時 Web 通訊:長/短輪詢、WebSockets 和 SSE 解釋 + Next.js 程式碼

Linda Hamilton
Linda Hamilton原創
2024-09-23 22:30:32791瀏覽

Real-Time Web Communication: Long/Short Polling, WebSockets, and SSE Explained + Next.js code

背景故事:意想不到的面試問題

幾個月前,我正在參加一個中級前端職位的技術面試。事情進展得很順利,直到我被一個讓我有點措手不及的問題擊中。

「想像一下,您需要一種持續的溝通形式來每秒檢查一些內容,直到檢索到您需要的內容。

例如,您想要不斷檢查付款是否成功,就像在電子商務設定中一樣。你會如何處理這個問題? ”

我謹慎地回答,「我認為你可以實作 WebSockets 來處理這個問題。」

面試官笑了。 「這是一個很好的解決方案,但根據情況還有其他可以說更好的選擇。」

就在那時,我們深入討論了各種即時通訊方法,包括長輪詢短輪詢WebSockets,最後, 伺服器發送事件(SSE),這可以說是單向資料流的最佳選擇,就像我們的付款範例一樣。

我們也討論了選擇正確的資料庫來處理這些持續但輕量級的請求,而不會耗盡伺服器資源。在這種背景下,Redis 應運而生,以其管理此類請求的簡單性和高效性而聞名。

這段對話讓我印象深刻。我意識到,雖然 WebSocket 受到了很多關注,但還有很多技術,只要被理解,就可以優化我們管理即時通訊的方式。今天,我想以清晰、引人入勝的方式分解這四種方法、何時使用每種方法以及它們的優缺點。最後,您將深入了解為什麼伺服器發送事件 (SSE) 經常在單向即時通訊方面大放異彩。

在開始之前,非常感謝Marcos,這位經驗豐富的高級軟體工程師進行了這次談話,並激勵我在幾個月後寫了這篇文章,儘管我沒有得到這份工作,但我還是非常感激! :)


實時通訊的四種方法

在進入 SSE 範例之前,我們先來分解我們在訪談中討論的四種方法:

1. 短輪詢

短輪詢可能是最簡單的方法。它涉及定期向伺服器發出請求,詢問「您有新資料嗎?」伺服器以當前狀態回應 - 無論是否有任何新內容。

正面:

  • 易於實作
  • 適用於傳統的 HTTP 請求

缺點:

  • 資源消耗量很大。即使沒有新資料可用,您也會頻繁發出請求。
  • 會增加伺服器負載和網路流量,這對於支付狀態更新等頻繁檢查來說會變得低效。

最適合:小型、低頻資料更新,例如每分鐘左右更新一次的股票市場價格。

2. 長輪詢

長輪詢比短輪詢更進一步。客戶端反覆向伺服器請求訊息,但伺服器不會立即回應,而是保持連線直到有新資料可用。一旦資料發回,客戶端立即開啟一個新連線並重複此過程。

正面:

  • 比短輪詢更有效率,因為伺服器僅在必要時回應 - 它真的很快。
  • 相容瀏覽器和HTTP/HTTPS協定。

缺點:

  • 仍然需要重複重新打開連接,隨著時間的推移會導致效率低下 - 資源昂貴。
  • 比短輪詢稍微複雜一些。

最適合:需要即時通訊但 WebSockets/SSE 可能太過分的情況(例如聊天應用程式)。

3. WebSocket

WebSockets 是一種更現代的解決方案,可在客戶端和伺服器之間提供全雙工通訊。連線開啟後,雙方都可以自由發送數據,無需重新建立連線 - 這定義了雙向通訊。

正面:

  • 真正的即時通信,延遲最小。
  • 非常適合雙向通訊(例如即時遊戲、聊天應用程式)。

缺點:

  • 實現起來比輪詢或 SSE 更複雜。
  • WebSocket 並不總是適合單向通訊或不太頻繁的更新,因為它們可以透過維護開放連線來消耗資源。
  • 可能需要防火牆配置。

最適合:需要持續雙向通訊的應用程序,例如多人遊戲、協作工具、聊天應用程式或即時通知。

4. 伺服器發送事件(SSE)

最後,讓我們來看看伺服器發送事件(SSE),它是我們付款範例的主角。 SSE 建立一種單向連接,伺服器將更新傳送到客戶端。與 WebSocket 不同,這是單向的——客戶端不會發回資料。

正面:

  • 非常適合單向資料流,例如動態消息、股票行情或付款狀態更新。
  • 比 WebSocket 更輕且更容易實現。
  • 使用現有的 HTTP 連接,因此支援良好且防火牆友善。

缺點:

  • 不適合雙向通訊。
  • 某些瀏覽器(尤其是舊版的 IE)不完全支援 SSE。

最適合:即時更新,客戶端只需要接收數據,例如即時比分、通知和我們的付款狀態範例。


SSE 實踐:使用 Next.js 即時支付狀態

讓我們進入問題的核心。我建立了一個簡單的 Next.js 應用程式來使用 伺服器發送事件 (SSE) 模擬即時支付流程。它準確地示範如何設定單向通訊來檢查付款狀態並在付款成功或失敗時通知用戶。

為 Next 設定它有點頭痛,因為它的工作方式與普通 js 有點不同,所以你稍後可以感謝我!

設定如下:

前端:事務控制組件

在下面的元件中,我們有一個簡單的 UI,顯示按鈕來模擬來自實際網關 API 的不同類型的交易(Pix、Stripe 和失敗的信用卡付款)。這些按鈕透過上交所觸發即時支付狀態更新。

這就是 SSE 魔法發生的地方。當模擬支付時,客戶端開啟一個SSE連線來監聽來自伺服器的更新。它處理不同的狀態,如待處理、傳輸中、已付款和失敗。

"use client";

import { useState } from "react";
import { PAYMENT_STATUSES } from "../utils/payment-statuses";

const paymentButtons = [
  {
    id: "pix",
    label: "Simulate payment with Pix",
    bg: "bg-green-200",
    success: true,
  },
  {
    id: "stripe",
    label: "Simulate payment with Stripe",
    bg: "bg-blue-200",
    success: true,
  },
  {
    id: "credit",
    label: "Simulate failing payment",
    bg: "bg-red-200",
    success: false,
  },
];

type transaction = {
  type: string;
  amount: number;
  success: boolean;
};

const DOMAIN_URL = process.env.NEXT_PUBLIC_DOMAIN_URL;

export function TransactionControl() {
  const [status, setStatus] = useState<string>("");
  const [isProcessing, setIsProcessing] = useState<boolean>(false);

  async function handleTransaction({ type, amount, success }: transaction) {
    setIsProcessing(true);
    setStatus("Payment is in progress...");

    const eventSource = new EventSource(
      `${DOMAIN_URL}/payment?type=${type}&amount=${amount}&success=${success}`
    );

    eventSource.onmessage = (e) => {
      const data = JSON.parse(e.data);
      const { status } = data;

      console.log(data);

      switch (status) {
        case PAYMENT_STATUSES.PENDING:
          setStatus("Payment is in progress...");
          break;
        case PAYMENT_STATUSES.IN_TRANSIT:
          setStatus("Payment is in transit...");
          break;
        case PAYMENT_STATUSES.PAID:
          setIsProcessing(false);
          setStatus("Payment completed!");
          eventSource.close();
          break;
        case PAYMENT_STATUSES.CANCELED:
          setIsProcessing(false);
          setStatus("Payment failed!");
          eventSource.close();
          break;
        default:
          setStatus("");
          setIsProcessing(false);
          eventSource.close();
          break;
      }
    };
  }

  return (
    <div>
      <div className="flex flex-col gap-3">
        {paymentButtons.map(({ id, label, bg, success }) => (
          <button
            key={id}
            className={`${bg} text-background rounded-full font-medium py-2 px-4
            disabled:brightness-50 disabled:opacity-50`}
            onClick={() =>
              handleTransaction({ type: id, amount: 101, success })
            }
            disabled={isProcessing}
          >
            {label}
          </button>
        ))}
      </div>

      {status && <div className="mt-4 text-lg font-medium">{status}</div>}
    </div>
  );
}

後端:Next.js 中的 SSE 實現

在伺服器端,我們透過 SSE 發送定期狀態更新來模擬支付流程。隨著交易的進行,客戶將收到有關付款是否仍在等待、已完成或失敗的更新。

import { NextRequest, NextResponse } from "next/server";

import { PAYMENT_STATUSES } from "../utils/payment-statuses";

export const runtime = "edge";
export const dynamic = "force-dynamic";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function GET(req: NextRequest, res: NextResponse) {
  const { searchParams } = new URL(req.url as string);
  const type = searchParams.get("type") || null;
  const amount = parseFloat(searchParams.get("amount") || "0");
  const success = searchParams.get("success") === "true";

  if (!type || amount < 0) {
    return new Response(JSON.stringify({ error: "invalid transaction" }), {
      status: 400,
      headers: {
        "Content-Type": "application/json",
      },
    });
  }
  const responseStream = new TransformStream();
  const writer = responseStream.writable.getWriter();
  const encoder = new TextEncoder();
  let closed = false;

  function sendStatus(status: string) {
    writer.write(
      encoder.encode(`data: ${JSON.stringify({ status, type, amount })}\n\n`)
    );
  }

  // Payment gateway simulation
  async function processTransaction() {
    sendStatus(PAYMENT_STATUSES.PENDING);

    function simulateSuccess() {
      setTimeout(() => {
        if (!closed) {
          sendStatus(PAYMENT_STATUSES.IN_TRANSIT);
        }
      }, 3000);

      setTimeout(() => {
        if (!closed) {
          sendStatus(PAYMENT_STATUSES.PAID);

          // Close the stream and mark closed to prevent further writes
          writer.close();
          closed = true;
        }
      }, 6000);
    }

    function simulateFailure() {
      setTimeout(() => {
        if (!closed) {
          sendStatus(PAYMENT_STATUSES.CANCELED);

          // Close the stream and mark closed to prevent further writes
          writer.close();
          closed = true;
        }
      }, 3000);
    }

    if (success === false) {
      simulateFailure();
      return;
    }

    simulateSuccess();
  }

  await processTransaction();

  // Return the SSE response
  return new Response(responseStream.readable, {
    headers: {
      "Access-Control-Allow-Origin": "*",
      Connection: "keep-alive",
      "X-Accel-Buffering": "no",
      "Content-Type": "text/event-stream; charset=utf-8",
      "Cache-Control": "no-cache, no-transform",
      "Content-Encoding": "none",
    },
  });
}

此外,請確保新增包含以下內容的 .env.local 檔案:

NEXT_PUBLIC_DOMAIN_URL='http://localhost:3000'

在這種情況下為什麼選擇 SSE 而非 WebSocket?

現在我們已經了解如何實現它,您可能想知道:為什麼要使用 SSE 而不是 WebSockets?原因如下:

  • 單向通訊:在我們的場景中,客戶端只需要接收有關付款狀態的更新。客戶端不需要不斷地將資料傳送回伺服器,因此 SSE 的簡單性非常適合。
  • 輕量級:由於 SSE 使用單一 HTTP 連線來串流更新,因此與維持全雙工通訊的 WebSocket 相比,它的資源效率更高。
  • 防火牆友善: SSE 更容易在不同的網路環境中使用,因為它透過 HTTP 運行,HTTP 通常在防火牆中開放,而 WebSocket 連線有時會遇到問題。
  • 瀏覽器支援: 雖然 SSE 的支援不如 WebSocket 廣泛,但現代瀏覽器都支援 SSE,這使得它對於大多數需要單向資料的用例來說都是可靠的。

結論:了解你的工具

那個面試問題變成了一次令人難以置信的學習經歷,讓我看到了長輪詢、短輪詢、WebSockets 和 SSE 之間的微妙差異。每種方法都有其時間和地點,了解何時使用哪種方法對於優化即時通訊至關重要。

SSE 可能不像 WebSockets 那樣迷人,但當涉及到高效的單向通訊時,它是完成這項工作的完美工具 - 就像我們的電子商務支付範例一樣。下次您建立需要即時更新的內容時,不要只是預設使用 WebSocket——考慮一下 SSE,因為它簡單且高效。

希望對即時通訊技術的深入研究能讓您對下一個專案或棘手的面試問題保持敏銳!


讓我們動手吧

Next.js + TypeScript 範例儲存庫:https://github.com/brinobruno/sse-next
Next.js + TypeScript 部署範例:https://sse-next-one.vercel.app/

參考

以下是一些權威來源和參考資料,您可以探索以獲得更深入的見解:

WebSockets 和 SSE 文件:

MDN Web 文件:WebSockets API
MDN Web 文件:使用伺服器傳送的事件

下一個 API 路由

Next.js:API 路由


讓我們聯絡吧

如果您想聯繫,我會分享我的相關社交活動:
GitHub
領英
投資組合

以上是即時 Web 通訊:長/短輪詢、WebSockets 和 SSE 解釋 + Next.js 程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn