在处理AI模型的流式输出时,通常使用 SSE 或者WebSocket, 各自优缺点?
AI 模型的流式输出本质是单向长文本推送,SSE 比 WebSocket 更合适,多数场景不需要双向通道。
- SSE:基于 HTTP,服务端到客户端单向推送,内置自动重连,零额外握手成本
- WebSocket:全双工双向通信,需要协议升级,实现复杂、资源开销大
- 选择逻辑:AI 问答是客户端发一条请求、服务端流式返回文本,单向通道完全够用
- 双向需求:语音对话、实时协作编辑才需要 WebSocket,纯文本问答不需要
- 坑 ①:HTTP/1.1 下同一域名最多 6 个 SSE 并发连接,多标签页可能占满
- 坑 ②:组件卸载时忘记手动关闭 EventSource,连接不会自动释放,导致内存泄漏
为什么 SSE 是默认选择
AI 聊天 99% 的流量模式是单向的——客户端发一条 JSON 请求,服务端流式吐文本回来。这个模式跟 SSE 的设计初衷完美匹配:服务端推送、客户端接收。
WebSocket 当然也能干这件事,但就像用货车送一封信——能送到,但没必要。SSE 基于标准 HTTP 协议,不需要协议升级握手,不需要额外的帧格式解析,代理和 CDN 天然兼容。
说白了,SSE 可以用在任何能发 HTTP 请求的地方。你用 curl 都能直接读 SSE 流,WebSocket 需要专门的客户端库。
SSE 的工作方式
SSE 通过 HTTP 长连接传输数据,Content-Type 是 text/event-stream。每条消息以 data: 开头,双换行结束:
data: {"token": "你好"}
data: {"token": ",世界"}
data: [DONE]
浏览器端的 API:
const es = new EventSource('/api/chat/stream');
es.onmessage = (e) => {
const { token } = JSON.parse(e.data);
// 渲染 token
};
内置自动重连,连接断了自己重试,不用写重连逻辑。
WebSocket 什么时候需要
WebSocket 的核心价值是全双工——客户端和服务端可以同时向对方发消息。
这在 AI 场景里什么时候有用?当客户端需要中途打断或控制服务端输出时:
- 用户点了"停止生成",需要立刻通知服务端取消推理
- 语音对话,音频数据双向传输
- 多人实时协作,同一个画布上多人同时编辑
纯文本问答不需要这些。用户发完问题就等着看回复,中途最多点个停止——这个"停止"可以通过 HTTP DELETE 单独发一个请求来实现,不需要为此建一个全双工通道。
WebSocket 的额外成本:协议升级需要一次 HTTP 握手再切换,连接状态管理更复杂,某些代理和负载均衡器对 WebSocket 支持不完善。
两个容易踩的坑
HTTP/1.1 的并发连接限制
浏览器对同一域名的 HTTP/1.1 并发连接上限是 6 个。每个 SSE 连接占用一个,也就是说同一域名下最多同时开 6 个 SSE 流。
在单页应用中通常不是问题——用户一次只在一个对话里。但如果用户习惯开多个标签页,或者产品设计里同时跑了几个 Agent 并行输出,SSE 连接数就可能占满,后续 HTTP 请求(包括 API 调用、图片加载)会被阻塞。
解法:用 HTTP/2 就没有这个限制了(HTTP/2 支持多路复用),或者用共享 Web Worker 管理 SSE 连接,避免多标签页各自开连接。
忘记关闭 EventSource
EventSource 不会在页面切换时自动关闭。SPA 里组件卸载了,如果没手动调用 close(),连接就一直活着——浏览器不帮你关,服务端也不知道客户端已经不需要了。
useEffect(() => {
const es = new EventSource('/api/chat/stream');
es.onmessage = (e) => {
// 处理消息
};
return () => es.close(); // 这行不能忘
}, []);
忘了这行,切几次页面后台就挂了一堆僵尸连接,服务端也在给没人听的客户端拼命发数据。内存泄漏就是这么来的。