![]()
2015年,瀏覽器廠商往JavaScript里塞了一行代碼,然后集體沉默。沒人教,文檔 bury 在角落,Stack Overflow 上搜到的全是 WebSocket 教程。結果過去十年,無數程序員為實時推送寫了成千上萬行重連邏輯、心跳檢測、狀態恢復——而答案早就躺在那兒,一行搞定。
這行代碼是 new EventSource('/events')。它做的事情和 WebSocket 很像,但哲學完全相反:WebSocket 是"你們隨便聊",EventSource(服務器發送事件,SSE)是"服務器說,客戶端聽"。
大多數實時場景根本不需要雙向對話。股價跳動、構建日志、通知紅點、直播彈幕——都是服務器推、客戶端收。用 WebSocket 做這些,就像為了收快遞專門雇個雙向對講的保安,SSE 則是門鈴響了去開門,完事。
一行代碼的暴力美學
EventSource 的 API 設計帶著一種老派的傲慢:連接、監聽、完事。
```javascript const source = new EventSource('/events'); source.onmessage = e => document.querySelector('#feed').textContent = e.data; ```
沒有 npm install,沒有 onclose 回調,沒有指數退避的重連算法。瀏覽器自動處理斷線重連,規范強制要求。你寫的代碼只管消費消息,基礎設施的臟活瀏覽器包了。
對比 WebSocket 的典型實現:onopen 握手、onerror 降級、onclose 啟動定時器、重連后比對消息 ID 補漏。這些代碼在無數項目里被復制粘貼,調試過凌晨三點的偶發斷連,而 SSE 的用戶根本沒意識到這些問題存在。
Rust 的 axum 框架里,SSE 端點長這樣:
```rust async fn events() -> Sse>> { let stream = stream::repeat_with(|| Event::default().data("update")) .map(Ok) .throttle(Duration::from_secs(1)); Sse::new(stream).keep_alive(KeepAlive::default()) } ```
![]()
返回一個流,運行時處理背壓、保活、客戶端斷開。沒有連接池管理,沒有 upgrade 握手,就是普通 HTTP。每個代理都認識它,每個 CDN 都能透傳它。
三個頭部搞定一切
SSE 的協議規范短得像個玩笑:
``` Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive ```
服務器發以 data: 開頭的行,瀏覽器解析。沒有幀編碼,沒有掩碼,沒有操作碼。文本流,換行分割,HTTP 從頭到尾。
WebSocket 為了兼容 HTTP 握手,先偽裝成 HTTP,再 upgrade 成獨立協議。這個設計在 2011 年情有可原——當時很多中間件不認識 WebSocket——但現在成了歷史包袱。防火墻、負載均衡、企業代理,每多一層都可能 upgrade 失敗。
SSE 不走 upgrade,它就是 HTTP。公司內網的老舊代理?認識。需要走 CDN 邊緣緩存?可以。想加個認證中間件?標準 Cookie/Header 直接復用。
協議簡單到作弊。
自動恢復:被低估的殺手特性
WebSocket 斷線后,開發者要面對一堆決策:多久重試?退避策略用線性還是指數?重連后怎么知道漏了哪些消息?要不要服務端維護消息隊列?
![]()
SSE 的規范直接規定了自動重連,瀏覽器實現它。斷線后,瀏覽器按退避算法自己連,連上后繼續收。服務端不需要為"客戶端剛重連"這個狀態寫特殊邏輯。
當然,消息丟失問題還在。但 SSE 支持 Last-Event-ID 頭部,重連時自動帶上最后收到的消息 ID,服務端可以據此補發。這個機制是可選的,簡單場景直接忽略,復雜場景按需實現——而不是像 WebSocket 那樣,重連邏輯是必答題。
一個類比:WebSocket 像租了輛跑車,動力強勁但得自己保養;SSE 像地鐵,時刻表固定,壞了等下一班,你不用管調度。
什么時候該用 WebSocket?
不是全面替代。聊天室、多人協同編輯、實時游戲——這些需要客戶端頻繁發消息的,WebSocket 的雙向通道更自然。但問問自己:你的功能真的需要雙向嗎?
Dashboard 實時數據、CI 構建日志、股票行情、社交 Feed、進度條——這些占"實時功能"的絕大多數。它們用 SSE 更輕、更穩、代碼更少。
有個細節很多人沒注意:SSE 天然支持瀏覽器的事件流接口,可以 addEventListener 監聽特定事件類型,做路由分發。WebSocket 收到消息要自己解析路由。
另一個被忽略的點:SSE 基于 HTTP,所以能享受 HTTP/2 的多路復用。一個 TCP 連接可以承載多個 SSE 流,瀏覽器自動調度。WebSocket 一個連接就是一個連接,開多了就是資源競爭。
技術選型里,"夠用且簡單"往往勝過"強大但復雜"。
原文作者 Vivian Voss 在文末留了句話:「One rather appreciates infrastructure that does not require a babysitter.」(人總該欣賞那些不需要保姆的基礎設施。)
這句話的刻薄之處在于,它暗示了很多程序員正在做的事情——寫重連邏輯、調心跳間隔、排查代理問題——本可以不存在。不是技術深度的問題,是信息差的問題。2015 年就有的標準,2025 年還在被忽視。
下次設計實時功能前,你會先問自己"真的需要雙向嗎",還是直接 npm install ws?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.