幾個月前,我正在參加一個中級前端職位的技術面試。事情進展得很順利,直到我被一個讓我有點措手不及的問題擊中。
「想像一下,您需要一種持續的溝通形式來每秒檢查一些內容,直到檢索到您需要的內容。
例如,您想要不斷檢查付款是否成功,就像在電子商務設定中一樣。你會如何處理這個問題? ”
我謹慎地回答,「我認為你可以實作 WebSockets 來處理這個問題。」
面試官笑了。 「這是一個很好的解決方案,但根據情況還有其他可以說更好的選擇。」
就在那時,我們深入討論了各種即時通訊方法,包括長輪詢、短輪詢、WebSockets,最後, 伺服器發送事件(SSE),這可以說是單向資料流的最佳選擇,就像我們的付款範例一樣。
我們也討論了選擇正確的資料庫來處理這些持續但輕量級的請求,而不會耗盡伺服器資源。在這種背景下,Redis 應運而生,以其管理此類請求的簡單性和高效性而聞名。
這段對話讓我印象深刻。我意識到,雖然 WebSocket 受到了很多關注,但還有很多技術,只要被理解,就可以優化我們管理即時通訊的方式。今天,我想以清晰、引人入勝的方式分解這四種方法、何時使用每種方法以及它們的優缺點。最後,您將深入了解為什麼伺服器發送事件 (SSE) 經常在單向即時通訊方面大放異彩。
在開始之前,非常感謝Marcos,這位經驗豐富的高級軟體工程師進行了這次談話,並激勵我在幾個月後寫了這篇文章,儘管我沒有得到這份工作,但我還是非常感激! :)
在進入 SSE 範例之前,我們先來分解我們在訪談中討論的四種方法:
短輪詢可能是最簡單的方法。它涉及定期向伺服器發出請求,詢問「您有新資料嗎?」伺服器以當前狀態回應 - 無論是否有任何新內容。
正面:
缺點:
最適合:小型、低頻資料更新,例如每分鐘左右更新一次的股票市場價格。
長輪詢比短輪詢更進一步。客戶端反覆向伺服器請求訊息,但伺服器不會立即回應,而是保持連線直到有新資料可用。一旦資料發回,客戶端立即開啟一個新連線並重複此過程。
正面:
缺點:
最適合:需要即時通訊但 WebSockets/SSE 可能太過分的情況(例如聊天應用程式)。
WebSockets 是一種更現代的解決方案,可在客戶端和伺服器之間提供全雙工通訊。連線開啟後,雙方都可以自由發送數據,無需重新建立連線 - 這定義了雙向通訊。
正面:
缺點:
最適合:需要持續雙向通訊的應用程序,例如多人遊戲、協作工具、聊天應用程式或即時通知。
最後,讓我們來看看伺服器發送事件(SSE),它是我們付款範例的主角。 SSE 建立一種單向連接,伺服器將更新傳送到客戶端。與 WebSocket 不同,這是單向的——客戶端不會發回資料。
正面:
缺點:
最適合:即時更新,客戶端只需要接收數據,例如即時比分、通知和我們的付款狀態範例。
讓我們進入問題的核心。我建立了一個簡單的 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> ); }
在伺服器端,我們透過 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 而不是 WebSockets?原因如下:
那個面試問題變成了一次令人難以置信的學習經歷,讓我看到了長輪詢、短輪詢、WebSockets 和 SSE 之間的微妙差異。每種方法都有其時間和地點,了解何時使用哪種方法對於優化即時通訊至關重要。
SSE 可能不像 WebSockets 那樣迷人,但當涉及到高效的單向通訊時,它是完成這項工作的完美工具 - 就像我們的電子商務支付範例一樣。下次您建立需要即時更新的內容時,不要只是預設使用 WebSocket——考慮一下 SSE,因為它簡單且高效。
希望對即時通訊技術的深入研究能讓您對下一個專案或棘手的面試問題保持敏銳!
Next.js + TypeScript 範例儲存庫:https://github.com/brinobruno/sse-next
Next.js + TypeScript 部署範例:https://sse-next-one.vercel.app/
以下是一些權威來源和參考資料,您可以探索以獲得更深入的見解:
MDN Web 文件:WebSockets API
MDN Web 文件:使用伺服器傳送的事件
Next.js:API 路由
如果您想聯繫,我會分享我的相關社交活動:
GitHub
領英
投資組合
以上是即時 Web 通訊:長/短輪詢、WebSockets 和 SSE 解釋 + Next.js 程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!