簡潔來說
伺服器發送事件 (SSE) 是基於純HTTP建構的單向流式通訊協議。瀏覽器與 new EventSource(url) 建立連接,伺服器保持連接開啟並在需要時推送 data: 行資訊。就是這樣。沒有 WebSocket 握手,沒有ws://,無法升級。它會自動重連,它可以通過每一個講HTTP的代理,而且它自2012年起就出現在每一個瀏覽器中。
如果你需要服務器→客戶端流式傳輸,而且你不需要雙向消息傳遞,SSE幾乎在2026年所有重要的方面都優於WebSockets: 代码更少,基礎設施更簡單,自動重連,自動事件 ID 以便可恢復。唯一需要使用 WebSockets 的原因是雙向聊天風格的流量或二進制框架。
想現在就看到一個流式處理嗎?打開免費的 SSE 測試工具,貼上任何 SSE 端點,實時觀察事件到達。
三十分鐘心智模型
[ Browser ] ──── GET /events ───► [ Server ]
◄── HTTP 200, keep alive
◄── data: hello\n\n
◄── data: world\n\n
◄── data: ...
這只是一個永不關閉的長生命週期HTTP GET,響應體上有特定的文字格式。MIME類型是text/event-stream。每兩行換行就將事件輸出到onmessage。瀏覽器處理框架、解析和重連——你編寫eventSource.onmessage = ...就完成了.
最小的可能範例
伺服器 (Node.js — 不需要任何函式庫):
import http from 'node:http';
http.createServer((req, res) => {
if (req.url !== '/events') {
res.writeHead(404).end();
return;
}
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
let n = 0;
const interval = setInterval(() => {
res.write(`data: tick ${++n}\n\n`);
}, 1000);
req.on('close', () => clearInterval(interval));
}).listen(3000);
客戶端 (任何地方 — 包括一個 <script> 標籤):
const es = new EventSource('/events');
es.onmessage = (e) => console.log('got:', e.data);
es.onerror = () => console.warn('disconnected, browser will retry');
運行伺服器,將客戶端貼到瀏覽器控制台,你可以在不到20行代碼中實現流式传输。沒有建立步驟。沒有圖書館。沒有升級協商. 這就是全部的說法.
為何 SSE 在 2026 年被低估
我見到團隊們一聽到「實時」就會反應過度地尋求 WebSockets,接著花上一週時間調試代理超時、粘性會話和 ALB 升級標頭。一半的情況下,用戶場景僅僅是服務器 → 客戶端——一個訊息源、一個通知抽屉、一個建構記錄、一個實時計數器。這就是 Server-Sent Events 的地盤。
這裡是使用 SSE 時你實際獲得免費的功能,而用 WebSockets 你必須親自建設:
1. 自動重連。 當連線中斷時,瀏覽器會等待約3秒後再重新連接。你不需要為此撰寫任何程式碼。使用WebSockets時,你會在每一個專案中為其餘的職業生涯撰寫相同的退避迴圈。
2. 事件識別碼和恢復。 如果伺服器發送id: 42\n,瀏覽器會在下次重新連接時將Last-Event-ID: 42作為標頭發送。伺服器可以從它停止的地方恢復。這是這個一個讓 SSE 非常適合用於建構記錄、AI 流式傳輸和審計資訊流的特性。透過 WebSockets,這是一個您可以自行設計的自訂協議。
3. 标準 HTTP 到處可用。SSE 是一GET與Accept: text/event-stream每個代理伺服器、每個CDN、每個WAF、每個反向代理都理解它(有一個例外——見下文)。Cookies,Authorization,CORS,壓縮 — 全部正常。WebSockets 需要升級的舞蹈,而代理常常錯誤處理它。
4. 沒有框架。 只有文本,行分隔。你可以 curl 一個 SSE 端點,並在終端中讀取流。試試用 WebSocket 這個。
curl -N -H "Accept: text/event-stream" https://api.example.com/events
SSE 帧的结构
格式非常簡單,但會讓人困惑。每個事件由一或多行組成,以兩個換行符(一個空白行)結束:
event: user-joined
id: 423
retry: 5000
data: {"userId":42,"name":"Ada"}
data: simple message
data: continuation of the same event
-
data:— 載荷。同一事件中的多個data:行會被\n連接在一起— -
event:設置事件名字聽聽看es.addEventListener('user-joined', ...). 若省略,則會引發錯誤onmessage. -
id:— 瀏覽器傳回的是什麼Last-Event-ID在重新連接時。 -
retry:— 等待重新連線的毫秒數。
這個必須有空行忘記第二個\n 是第一個 SSE 缺損。您的代理和客戶端看不到事件緩衝區。
應用 SSE 與 WebSockets 與長輪詢的時機
這是您唯一需要的決策樹:
當您應用 SSE 時:
- 流量僅為伺服器 → 客戶端(通知、資訊串流、儀表板、日誌、AI 令牌串流)
- 您希望自動重新連接而不需要編寫
- 你正在串流文字(JSON、markdown 段落、記錄行)
- 你想用
curl
當使用 WebSocket 時:
- 交通是雙向且高頻率(聊天、協作編輯、多人遊戲)
- 你需要二進位框架(音訊/視訊、自訂協議)
- 你需要客戶端 → 伺服器訊息的單次往返在 100 毫秒以下
當以下情況使用長輪詢:
- 您卡在會導致上述兩者都失效的基础設施上(在2026年很少見,但在積極的公司代理後會發生)
雙方都有代碼的完整故障分析在SSE vs WebSockets vs Long Polling 深入探討.
三個生產中的陷阱
這些是每個團隊在首次發布 SSE 時遇到的錯誤.
1. Proxy 缓存會讓你死
Nginx 預設會緩存回應。你的 res.write 會落在緩存中;客戶在數分鐘內看不到任何內容。兩個解決方案,都做:
伺服器回應標頭:
X-Accel-Buffering: no
Nginx 設定:
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
Cloudflare緩存過多 — 您需要將SSE終點放在非緩存路徑上,或使用支援Cloudflare的串流設定
2. 浏覽器的每個來源6個連接限制
瀏覽器限制您每個來源最多開啟~6個HTTP/1.1連接。如果您的應用程式開啟EventSource 和使用者開啟 5 個更多分頁,第七個分頁掛起來,因為沒有連接埠。兩個解決方法:
- 使用 HTTP/2 或 HTTP/3 — 連接多重處理意味著在單一 TCP 連接上建立數百個串流。這是現代的解決方法。
-
透過
BroadcastChannel或一個SharedWorker協調各個分頁 — 一個分頁持有 EventSource,向兄弟分頁廣播事件。
3. 心跳與闲置超時
與 WebSockets 相同的陷阱:負載平衡器會關閉闲置連接。AWS ALB 的預設值為 50 秒。每 15-30 秒發送一行評論:
setInterval(() => res.write(':keepalive\n\n'), 15000);
以 : 開頭的行是根據規範的評論 — 它們保持連接溫暖而不觸發onmessage 在客戶端上.
認證和 SSE
EventSource 有一個令人煩惱的限制: 你無法在構建時設置自定義標頭。沒有Authorization: Bearer ...。你的選項:
1. Cookies (推薦用於瀏覽器應用程式) — 瀏覽器自動發送它們。使用withCredentials: true並允許你的來源 CORS。
const es = new EventSource('/events', { withCredentials: true });
2. 查詢字串權杖 — 有效,但權杖會出現在伺服器日誌中.
3. The fetch + ReadableStream 模式 — 刪除 EventSource 以便 fetch,而 做到 接受自訂標頭。您會失去自動重連,必須自行解析格式,但對於使用 token 認證的現代應用程式來說,這是正確的選擇:
const res = await fetch('/events', {
headers: { Authorization: `Bearer ${token}` },
});
const reader = res.body.getReader();
// ... parse text/event-stream chunks manually
像 @microsoft/fetch-event-source 這樣的函式庫會幫您處理這些,而這也是大多數現代 AI 流式傳輸客戶端在底層使用的技術:
SSE 是驅動 AI 流式傳輸的力量
如果你在過去兩年裡使用過 ChatGPT、Claude 或任何 AI 聊天介面,你已經看過 SSE 的實際應用。伺服器在 token 生成時即進行串流:
data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{"content":" world"}}]}
data: [DONE]
Anthropic、OpenAI 和 Google 的串流 API 都使用 SSE(或非常相似的專有事件串流格式)。了解 SSE 意味著你可以使用相同的工具來調試 AI 串流端點,就像你用來調試任何其他網路協議的工具一樣。
如何測試 SSE 端點
三種方法,按使用順序排列:
1. curl -N — 不緩衝旗標。最快的基本檢查。
curl -N -H "Accept: text/event-stream" https://api.example.com/events
2. 瀏覽器內 SSE 測試工具 — 貼上 URL,實時查看事件,查看標頭,查看重新連接行為。免費使用一個,無需安裝.
3. 瀏覽器開發工具 → 網絡 → 事件流選項卡 — 一旦您的應用程式打開,您就可以看到解析後的事件串流實時顯示。基於 Chromium 的瀏覽器為此有專用的分頁。
常見錯誤觀念
- "SSE 已死亡,WebSocket 已取代它。" 網頁套接字已發展,但Server-Sent Events從未走過遠 — 它驅動AI串流、GitHub Actions日誌、Vercel部署日誌、Stripe網鈎(嗯,技術上不是SSE,但概念相同),以及你日常使用的大多數「通知」功能.
- "SSE無法與HTTP/2協議搭配。" 它與HTTP/2協議 — 沒有6個連接的限制,所有串流都會被多工處理. 更好
- "SSE 只支援文字,所以你無法傳送二進制檔案。" 是的,但將小的二進制有效載荷編碼為 base64 並放在 JSON 字段裡是沒問題的,而對於真正的二進制流,你本應使用 WebSockets 或直接透過 HTTP 下載.
-
"SSE 沒有 ping/pong 机制。" 每隔 15 秒發送一條註釋行 (
:heartbeat\n\n)。完成.
常見問題集
SSE 與長輪詢一樣嗎?
編號 長時間盤詢 = 客戶端發出請求,伺服器保持請求打開直到有數據,伺服器回應,客戶端發出另一個請求。Server-Sent Events (SSE) = 一個請求永遠保持打開,伺服器有數據時推送。長時間盤詢每次都重新建立連接。SSE 不會。
SSE 在移動瀏覽器上有效嗎?
是的,iOS Safari、Chrome Android、Firefox Mobile 完全支援。自動重連處理手機網絡切換很好。
我可以使用 SSE 來配合 React / Vue / Svelte / Solid 嗎?
可以,就像你使用任何訂閱一樣。在 EventSource 中啟動一個 useEffect (或相當的),在清理時關閉它,在每個訊息上設置狀態。
伺服器能夠處理的最大並發 SSE 連接數是多少?
使用 Node.js 和合理的設定,每個進程可處理數萬個連接 — 連接大多數時間是處於闲置狀態,每個連接只消耗幾 KB 的 RAM。相比之下,每個進程的實際上限是約 32k-65k 個開啟的檔案描述符。
我該使用 EventSource 還是 fetch + ReadableStream?
如果您可以使用 cookie 認證(更簡單,免費自動重連),請使用 EventSource。使用fetch 如果你需要 Authorization 標頭或自訂請求行為,並引入一個庫來處理重連.
原發表於 AllDevToolsHub。為了獲得250多個免費、注重隱私的瀏覽器基礎開發者工具——SSE測試器、WebSocket測試器、JWT解碼器等——請參考 alldevtoolshub.com。












