#Next.js#TypeScript

实时 Web:SSE 与 WebSocket 深度对比

服务端推送需求一来,就纠结 SSE 还是 WebSocket?本文讲清两者的本质区别(SSE 单向/HTTP/简单,WebSocket 双向/独立协议/灵活)、各自适合的场景、代理/防火墙兼容性、连接数限制,给一份"什么场景选哪个"的明确结论。

本文要点

  • 主题聚焦:Next.js、TypeScript
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年4月7日·更新于 2026年7月23日

但凡做过实时功能,这道选择题就绕不开:服务端要往浏览器推数据,用 SSE(Server-Sent Events) 还是 WebSocket?

网上大部分文章给的是"两者都能用,看需求"——这句正确但没用。真正要决策的时候,你想知道的是:它俩到底在协议层差在哪?穿代理谁更稳?为什么 LLM 流式输出几乎清一色用 SSE?什么时候必须上 WebSocket?本文把这些问题一次讲透。

一、本质区别:协议层面的根本不同

先抛开 API,看协议。这是所有差异的源头。

SSE 不是新协议,它就是 HTTP。 客户端发起一个普通 HTTP GET 请求,服务端不关连接,持续往响应体里写数据,格式是简单的 data: xxx\n\n。浏览器通过 EventSource 这个原生 API 接收。

text
GET /events HTTP/1.1
Accept: text/event-stream
---
HTTP/1.1 200 OK
Content-Type: text/event-stream

data: {"price": 4200}

data: {"price": 4201}

WebSocket 是独立协议。 它借 HTTP 做一次"升级握手"(101 Switching Protocols),握手成功后,那条 TCP 连接就被"劫持"了,从此不再说 HTTP,改说 WebSocket 自己的二进制帧协议,而且是全双工——客户端和服务端可以同时、随时互发消息。

text
GET /ws HTTP/1.1
Upgrade: websocket
---
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
(此后该 TCP 连接脱离 HTTP,进入双向帧通信)

一句话总结:SSE 是"服务端单向往浏览器推"的 HTTP 长连接;WebSocket 是"双向实时"的独立协议。 方向性、协议、复杂度,三者一体。

二、SSE 的优势:简单、HTTP 友好

SSE 的优点几乎全部来自"它就是 HTTP"这一点。

第一,穿防火墙、反向代理、CDN 几乎零成本。 因为它就是普通 HTTP 请求/响应,nginx、Cloudflare、企业内网代理默认都能正确处理。不需要额外配置协议升级,不需要担心某个中间盒不认识 WebSocket 握手包。

第二,浏览器原生自动重连。 EventSource 连接断了,浏览器会自动尝试重连,还会带上 Last-Event-ID 头,服务端据此恢复断点续传。这是协议内置能力,你不用自己写重连逻辑。

ts
// 客户端:三行代码搞定
const es = new EventSource('/api/events');
es.onmessage = (e) => console.log(JSON.parse(e.data));
es.onerror = () => { /* 浏览器会自动重连,不用手动处理 */ };

第三,API 极简。 服务端只要设置正确的响应头,然后往流里写文本即可;客户端 EventSource 用 onmessage/addEventListener 就能收消息。心智负担极低。

第四,HTTP/2 下天然多路复用。 多个 SSE 流可以复用同一条 HTTP/2 连接,不会像 HTTP/1.1 那样撞连接数上限(详见下一节)。

三、SSE 的劣势:单向、文本、连接数限制

劣势也来自"单向 HTTP"这个本质。

第一,单向。 服务端→客户端可以推,客户端→服务端只能走普通 HTTP(发个 POST)。如果你想做聊天、协作编辑这种"双方互发"的场景,SSE 会非常别扭——得开两条路,上行 HTTP、下行 SSE,状态还得自己同步。

第二,文本为主。 SSE 传的是 UTF-8 文本,二进制数据要 Base64 编码,有膨胀开销。传图片、音视频流不合适。

第三,同域连接数限制(HTTP/1.1 下)。 浏览器对同一域名的 HTTP/1.1 连接数有上限(Chrome/Firefox 一般是 6 个)。每个 SSE 占一条连接,开 6 个 SSE 就可能把 HTTP/1.1 的连接池占满,连普通请求都卡住。这个限制在 HTTP/2 下消失,所以生产环境务必走 HTTP/2。

另外,EventSource 只支持 GET、不能自定义请求头(比如塞 Bearer token 进去),鉴权通常得靠 Cookie 或 URL 参数,稍微有点别扭。

四、WebSocket 的优势:双向、二进制、低延迟

WebSocket 的优点,正是 SSE 的短板所在。

第一,全双工双向。 这是它存在的核心理由。客户端、服务端可以随时互发消息,天然适合聊天、协作、多人游戏、实时白板这类"你来我往"的场景。

第二,二进制友好。 WebSocket 帧可以传 ArrayBuffer/Blob,做游戏同步、音视频传输、大量结构化二进制数据没有编码膨胀。

第三,协议开销低、延迟低。 握手之后,每个数据帧头部只有 2-14 字节,比每次 HTTP 请求带一坨头要轻得多。高频小消息(比如位置同步)用 WebSocket 明显更省。

ts
// 客户端
const ws = new WebSocket('wss://example.com/ws');
ws.onmessage = (e) => console.log(e.data);
ws.send(JSON.stringify({ type: 'chat', text: 'hi' })); // 双向!

五、WebSocket 的劣势:穿代理复杂、状态管理全靠自己

第一,独立协议,穿代理和 CDN 麻烦。 虽然握手借了 HTTP,但中间任何一环(企业代理、某些 CDN、负载均衡)不支持 Upgrade 就断链。nginx 要单独配 proxy_set_header Upgrade $http_upgrade;,Cloudflare 有一堆 WebSocket 专属注意事项,HTTP/2 代理还涉及 :protocol 扩展(Connect/扩展协商)。SSE 在这方面几乎不用操心。

第二,基础设施成本高。 WebSocket 是有状态长连接,服务端要维持每个连接的状态(用户、订阅、心跳)。这意味着负载均衡不能简单轮询,水平扩展要引入 Redis Pub/Sub 之类的消息总线来跨节点广播,部署复杂度上一个台阶。

第三,所有"可靠性"都得自己造。 浏览器原生 WebSocket 不会自动重连,你得自己写重连+退避策略;协议层没有心跳,你得定期 ping/pong 探活,防止连接被中间网络设备悄悄掐断;鉴权也完全自定义(常见做法是握手时带 token 或在子协议里塞)。这些 SSE 大多开箱即用。

六、选型决策:什么场景选哪个

把上面的差异压成一张对比表:

维度SSEWebSocket
通信方向服务端→客户端(单向)全双工双向
底层协议HTTP独立协议(借 HTTP 握手)
数据格式文本(UTF-8)文本 + 二进制
浏览器 APIEventSource(原生,自动重连)WebSocket(需自己重连)
代理/CDN/防火墙友好(就是 HTTP)需额外配置
连接数限制HTTP/1.1 同域约 6 个(HTTP/2 无此限)无浏览器层面的硬性限制
心跳/重连/鉴权协议内置或简单全部自己实现
服务端复杂度低(就是写 HTTP 响应流)高(有状态长连接)
水平扩展相对简单需消息总线
典型场景通知、行情、日志流、AI 流式聊天、协作、多人游戏

决策结论,三句话:

  1. 单向推送 → SSE。 站内通知、股票行情、构建日志流、AI token 流式输出,服务端只管推、客户端只管收,选 SSE。简单、可靠、基础设施零负担。
  2. 双向实时 → WebSocket。 即时聊天、协作编辑、多人游戏、实时同步,客户端也要频繁往服务端发消息,选 WebSocket。别用两条 SSE/HTTP 去硬凑,状态同步会让你写到怀疑人生。
  3. 不确定就先上 SSE。 SSE 的实现成本是 WebSocket 的零头。先用 SSE 跑起来,真撞到"客户端要高频回传"的天花板,再升级到 WebSocket。大部分实时需求,你根本走不到升级那一步。

七、AI 应用的启示:为什么 LLM 流式输出都选 SSE

这一波 AI 应用,几乎重新定义了 SSE 的存在感。你打开 OpenAI、Anthropic、Google、Vercel AI SDK 的流式接口,底层清一色是 SSE——这不是巧合。

原因正是 SSE 的本质特征和 LLM 流式输出完美匹配:

  • LLM 流式就是单向推送。 模型生成一个 token、推一个 token 给前端,前端不需要在生成过程中反向发消息。单向 = SSE 的主场。
  • 基于 HTTP = 基础设施免费。 LLM 服务通常跑在标准 HTTP 网关后面,用 SSE 等于直接复用现有 CDN、鉴权、限流、可观测性体系,不用为 WebSocket 单独搭一套。
  • 简单可靠。 断线自动重连、text/event-stream 格式易调试、浏览器原生支持。对于一个本身就有不确定性(模型可能超时、限流、内容审核)的系统,传输层越简单越好。
  • 生态标准化。 OpenAI 的 stream: true、Vercel AI SDK 的 useChat、各种 LangChain 流式封装,默认就是 SSE。选 SSE = 站在主流生态这边。

典型的 Next.js 流式响应,服务端就是一个 Route Handler 往下吐 SSE:

ts
// app/api/chat/route.ts (示意)
export async function POST(req: Request) {
  const stream = new ReadableStream({
    async start(controller) {
      const encoder = new TextEncoder();
      for await (const token of llmStream(await req.json())) {
        controller.enqueue(encoder.encode(`data: ${JSON.stringify({ token })}\n\n`));
      }
      controller.enqueue(encoder.encode('data: [DONE]\n\n'));
      controller.close();
    },
  });
  return new Response(stream, {
    headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
  });
}

前端拿到流以后,边读边渲染打字机效果。整条链路没有一个 WebSocket。这也是为什么——AI 时代,SSE 迎来了第二春。

小结

  1. 本质差异在协议:SSE 是 HTTP 长连接(单向),WebSocket 是独立协议(全双工)。所有优缺点都从这里派生。
  2. SSE 胜在简单和 HTTP 友好:穿代理/CDN 零配置、自动重连、API 极简,基础设施成本最低。
  3. WebSocket 胜在双向和性能:全双工、二进制、低帧开销,是聊天/协作/游戏的必需品。
  4. WebSocket 代价不低:独立协议穿代理要配置、有状态长连接水平扩展难、重连/心跳/鉴权全自己造。
  5. AI 流式输出来救场:LLM 流式本质是单向推送,加上生态标准化,SSE 成为事实主流。

金句:需求是单向的,SSE 就够了;只有双向才需要 WebSocket——而 90% 的"实时"需求,其实是单向的。

附带一句实操提醒:SSE 务必配 HTTP/2,绕开 HTTP/1.1 那 6 个连接的天花板;WebSocket 务必配好心跳和重连,别让连接在网络中间悄无声息地死掉。这两条踩稳,选型就不会翻车。

Related

相关推荐