假设你是一家高档日料店的常客。以前,你点一份套餐,后厨必须把所有菜品——前菜、刺身、天妇罗、甜品——全部备齐,才由服务员一次性端上桌。你饿着肚子干等十五分钟,而真正好吃的第一贯寿司早就该在五分钟时送到你嘴里了。这就是传统 HTTP 请求的体验:请求发出去,服务器默默憋大招,最后“哗”地一下把完整响应砸回来。 现在,这家店换了经营模式:吧台后面多了一条旋转传送带。后厨每捏好一贯寿司,立刻放在传送带上,你眼前随时出现刚出锅的美食,边吃边等,体验拉满。这就是 SSE(Server-Sent Events)——服务器像传送带一样,把数据“一条一条”推到客户端,让大模型的回答像打字一样逐字呈现。 本文带你彻底搞懂 SSE 的协议原理、与 WebSocket 的区别,以及如何在 Python 中亲手实现一个 SSE 流式接口。读完你不仅能看懂大模型聊天界面的底层机制,还能自己动手写一个。

岗位关联

对 LLM 应用工程师而言,SSE 是撬动“实时交互体验”的杠杆——掌握它,你就能开发出像 ChatGPT 一样逐字输出的对话应用,这是 AI 产品落地的核心技能之一。

目录
  1. 可视化演示
  2. 为什么需要流式输出
  3. SSE 协议核心机制
  4. SSE 与 WebSocket 对比
  5. Python 实现 SSE 接口
  6. SSE 在 LLM 应用实战
  7. SSE 局限与最佳实践
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示SSE流式输出中Token逐字推送过程、SSE与WebSocket单向/双向通信对比,以及EventSource事件流格式解析。

本节配有交互动效(进入视口后自动加载,约 3KB)。

流式传输:Token逐字推送 服务器逐Token推送,客户端实时渲染 每个Token间隔~120ms模拟LLM生成 首字延迟低,用户体验显著提升 TTFB \u2248 200ms,逐字呈现无需等待全量

为什么需要流式输出

你有没有注意过,在 ChatGPT 问世之前,几乎所有的“AI 问答”功能都是一个死板的流程:你点击“提交”按钮,页面转圈,三五秒甚至十几秒后,一整段文字“啪”地出现在屏幕上。可 ChatGPT 不一样——它像一位打字飞快的秘书,你在屏幕上看着一个字一个字被打出来。这两种体验的差别,就像“在餐厅里饿着肚子等一桌菜上齐”和“坐在吧台前看着寿司师傅现捏现送”。

这种体验上的革命,背后就是流式输出技术。传统 HTTP 请求是一次性的:客户端发一个请求,服务器把所有数据处理完,再一次性返回完整响应。在这个模型下,大模型生成 500 字回答需要 10 秒,用户就必须对着转圈的空屏等 10 秒。注意,这 10 秒里用户其实完全无事可做——而这 10 秒恰恰是“对话感”消失的元凶。

流式输出彻底改变了这个体验。服务器一旦开始生成内容,就立刻把已经生成的片段发送给客户端,用户看到的不是“一张白纸等 10 秒”,而是“屏幕上第 1 秒就开始蹦字”。心理学上有个概念叫“感知性能”——用户对等待的忍受度,往往取决于有没有进度反馈。流式输出让“等待”变成了“观看过程”,把 10 秒的煎熬变成 10 秒的期待。

所以,流式输出解决的不是“生成速度问题”,而是“等待体验问题”——它把后厨悄悄备菜,变成了开放厨房里的现场料理秀。对你未来的 AI 产品而言,这可能就是用户留存率的分水岭。

感知等待时间=实际等待时间进度反馈密度\text{感知等待时间} = \frac{\text{实际等待时间}}{\text{进度反馈密度}}
公式解读:分母是“用户看到的反馈频率”。反馈越密集(比如逐字蹦字),用户觉得等待时间越短。流式输出就是把这个分母拉到最大。
  1. 感知等待时间用户主观感受到的等待时长,由实际等待时间和进度反馈密度共同决定。
    感知等待时间\text{感知等待时间}
  2. 实际等待时间从发起到得到完整结果的真实耗时,流式输出不能减少它,但能“稀释”它。
    实际等待时间\text{实际等待时间}
  3. 进度反馈密度单位时间内用户看到的反馈次数,逐字输出让这个值接近无穷大。
    进度反馈密度\text{进度反馈密度}

SSE 协议核心机制

想象这条传送带本身:它看似一直在动,但物理结构只是一条固定在吧台上的滚轴带,不需要每次传菜都重新架一条。SSE 就是这条固定的传送带——客户端和服务器之间只建立一个 HTTP 连接,然后这个连接一直保持着,服务器源源不断地把数据放在上面推给客户端。

SSE 全称 Server-Sent Events(服务器发送事件),名字直白:就是“服务器发送过来的事件”。它基于普通的 HTTP 协议,但使用了 Content-Type: text/event-stream 这个特殊的 MIME 类型来标记响应。一旦浏览器看到这个类型,就知道“这不是一次性响应,而是会持续推送的流”。连接建立后,服务器可以随时向客户端发送消息——注意,是“随时”,不需要客户端再次请求。

SSE 的消息格式非常有规律,像传送带上的寿司碟子,每碟都有固定的摆盘规则。一条 SSE 消息由若干字段行组成,最常见的是 data: 字段;消息之间用空行分隔。举个最简例子:

这一行 data: 你好 加一个空行,就是一条完整的 SSE 消息。服务器可以持续发送多个这样的块。浏览器端通过 EventSource API 接收,每收到一条 data 消息,就会触发一个 onmessage 回调。一个用起来异常简单的 API——不需要处理复杂的状态码,不需要握手升级协议。

SSE 还内置了两个贴心机制:自动重连和 Last-Event-ID。如果连接意外断开,浏览器会自动重新发起连接;服务器还可以通过 id: 字段告诉客户端“你断到哪了”,重连时客户端带上这个 ID,服务器就能从断点续传。这让 SSE 在弱网环境下依然有不错的鲁棒性——传送带断了,拼接好又继续转。

\text{SSE 消息} = \text{"data: " + 内容 + "\n\n"}
公式通俗说:每条 SSE 消息就是在内容前面加 "data: " 前缀,后面加一个空行表示消息结束。
  1. `data` 前缀:标记这行是消息内容的字段名,类似传送带上的碟子底部印着的“三文鱼”标签。
    data: 内容\text{data: 内容}
  2. 空行 \n\n表示消息结束的分隔符,服务器发完一条消息后必须跟一个空行,浏览器才认为“这条消息完整了”。
    \text{"\n\n"}
  3. 流式传输连接不是一次性关闭的,服务器可以持续发送多条 data: 内容\n\n,如同传送带不断上菜。
    消息1+消息2+⋯\text{消息}_1 + \text{消息}_2 + \cdots
text1 行
data: 你好

SSE 与 WebSocket 对比

选通信协议,有点像选择用餐方式:如果你只是坐在吧台前等主厨一道道把菜递给你,SSE 的“单向传送带”绰绰有余;但如果你需要跟主厨讨论“这道菜少放盐、那道菜多加点芥末”,就需要一种双向对话机制——这正是 WebSocket 的用武之地,它是一条允许双方随时往对方盘子里夹菜的双向通道。

先说协议层面。WebSocket 是一个独立的协议(ws:// 和 wss://),它需要在 HTTP 之上进行一次“升级握手”,把协议从 HTTP 切换为 WebSocket 协议。而 SSE 本质还是 HTTP——它没有协议升级,只是把连接的响应方式从“一次性”变成了“持续推送”。这个差异带来一个关键后果:SSE 天然兼容现有的 HTTP 基础设施(比如负载均衡、防火墙通常都放行 HTTP),而 WebSocket 在某些代理环境里可能会被拦。

再说通信模式。WebSocket 是双向的,客户端和服务器都可以随时发起消息;SSE 是单向的,只能服务器向客户端推。这带来一个微妙的工程权衡:SSE 的模型极简——你不需要处理来自客户端的随时消息,少了一整类并发问题。但代价是:如果你的应用场景需要客户端频繁向服务器发消息(比如多人实时协作编辑器),SSE 就得再开一个普通 HTTP 通道配合使用,绕了一个弯。

WebSocket 就像一个高级的双方对讲机,什么都好,但你要是用它来单向喊话,就杀鸡用了牛刀。SSE 用最小的机制做最合适的事——对 LLM 应用而言,模型生成回答本来就是单向的,SSE 是性价比最高的选择。

选型指标=w1×单向性+w2×复杂度+w3×兼容性\text{选型指标} = w_1 \times \text{单向性} + w_2 \times \text{复杂度} + w_3 \times \text{兼容性}
公式白话版:选择协议就像权衡“够用”与“功能”——大模型输出是“服务器单方面持续输出”,天然适配单向的 SSE,何必动用更重型的 WebSocket。
  1. 单向性得分大模型对话只需要服务器向客户端推流,SSE 在“单向够用”这个维度上打高分。
    单向性\text{单向性}
  2. 复杂度得分SSE 无需协议升级、无需心跳包(HTTP 层自动处理),工程复杂度远低于 WebSocket。
    复杂度\text{复杂度}
  3. 兼容性得分SSE 走标准 HTTP 端口,能穿透绝大多数企业代理,浏览器的 EventSource API 内置自动重连。
    兼容性\text{兼容性}

Python 实现 SSE 接口

纸上谈兵容易,真正的问题来了:怎么在你的 Python 后端里端出这条传送带?现代 Web 框架几乎都支持 SSE。这里我们用 FastAPI 来演示——它原生支持异步流式响应,语法异常简洁。FastAPI 是一个基于 Python 类型注解的现代 Web 框架,相当于餐厅的中央厨房管理系统,你只需要告诉它“这道菜走传送带上”。

SSE 的原理不复杂,实现也谈不上尖端,但真正的难点在于正确设置响应头和用生成器产出数据。核心点:生成器是一种惰性求值的函数对象——普通函数是一次性把所有结果算完返回,而生成器一次只产出(yield)一个值,然后暂停。当你在生成器里循环遍历大模型的输出时,每个 yield 都会立即把内容推送到客户端,这就是 SSE 流式的魔法所在。

服务端代码的核心骨架如下:定义一个生成器函数,在循环中 yield 出符合 SSE 格式的字符串(即 data: ...\n\n),然后用 StreamingResponse 包装给 FastAPI——这个类就像一个贴好标签的传菜窗口,它知道要用 text/event-stream 来标识这次响应。客户端收到这个响应头后,就知道“这条连接会持续上菜,不会等一次送完”。

就这么简单——SSE 接口的实现本质是一个生成器 + 一个响应头。但要注意,这只是“能用”的起点。实际生产环境还需要处理客户端断开、代理缓冲、并发控制等问题,这些在后面的章节展开。先把这个骨架搭出来跑通,再持续加固。

StreamingResponse=generator(tokens)+Content-Type: text/event-stream\text{StreamingResponse} = \text{generator}(\text{tokens}) + \text{Content-Type: text/event-stream}
公式通俗说:把生成器吐出的 token 作为传输内容,同时告诉浏览器“我用的是 event-stream 格式”。
  1. 生成器一个产出 token 的迭代器,每次 yield 出一个 token,相当于传送带每次放一个寿司上去。
    generator=for token in tokens: yield "data: " + token + "nn"\text{generator} = \text{for token in tokens: yield "data: " + token + "\\n\\n"}
  2. text/event-stream 响应头告诉客户端“这是 SSE 流”,浏览器或前端框架据此解析消息边界而非直接关闭连接。
    Content-Type: text/event-stream\text{Content-Type: text/event-stream}
  3. StreamingResponse框架提供的响应包装器,接收生成器对象,在连接不断开的前提下持续发送数据。
    StreamingResponse(generator)\text{StreamingResponse}(\text{generator})

SSE 在 LLM 应用实战

现在你已经掌握了传送带的原理,是时候看看它在真实餐厅里的运转了。最经典的场景当然是大模型对话应用——ChaiGPT、Claude、文心一言的网页界面,你输入一句话后看到的逐字输出,底层几乎都是 SSE 在发力。这条传送带从 Llama 或 GPT 模型推理服务开始,把一个个 token(最小文本单位)传送到你的浏览器上。

具体流程拆解:前端用 EventSource 或 fetch 的流式模式发起请求;后端收到请求后向 LLM 推理服务发出调用,两者之间通常也走 SSE;后端拿到模型的 token 流后,用生成器逐字取出内容,再以 SSE 格式转发给前端——这就像传送带在中转站接驳,一路直达用户。前端每收到一条 data: 消息,就追加一段文字到页面上,于是用户看到的就是“AI 在思考中打字”。

SSE 的另一个实用战场是实时进度上报。想象你有一个“文章摘要批处理系统”,用户提交 100 篇文档,系统要逐一处理。传统做法是提交后页面上一个进度条旋转半天;用 SSE,服务器每处理完一篇文档就推送一条进度消息,前端实时更新百分比,用户体验瞬间提升一个档次。这种用法不需要大模型,但 SSE 的单向推送特性在这里恰如其分。

掌握了这条链路,你就能自己动手做一个和大厂体验相当的 AI 对话应用了。传送带就是消息通道,LLM 就是主厨,而你要做的就是把两者接得溜光顺滑。

LLM 对话时序=用户输入→后端转发→模型生成→SSE 逐字推送\text{LLM 对话时序} = \text{用户输入} \rightarrow \text{后端转发} \rightarrow \text{模型生成} \rightarrow \text{SSE 逐字推送}
公式白话版:用户输入触发模型生成,模型每吐一个字,SSE 就把这字推到用户屏幕,形成“打字机”效果。
  1. 用户输入到后端前端将用户的问题通过普通 HTTP POST 发送给后端。
    POST /chat→{messages}\text{POST /chat} \rightarrow \{\text{messages}\}
  2. 后端转发给 LLM后端将对话历史拼接成 Prompt,转发给推理服务器(如 vLLM 或 OpenAI API)。
    backend→LLM API\text{backend} \rightarrow \text{LLM API}
  3. LLM 流式生成推理服务器开启 stream=true,按 token 产出内容。
    LLM 输出: token1,token2,…\text{LLM 输出: token}_1, \text{token}_2, \ldots
  4. SSE 逐字推送后端把每个 token 包装成 SSE 消息推给浏览器。
    \text{data: token}_n \text{\n\n} \rightarrow \text{浏览器}

SSE 局限与最佳实践

传送带也不是万能的。如果有一天客人想跟主厨实时对话、调整口味,单向的传送带就乏力了——因为 SSE 只能从服务器到客户端单向流动,客户端无法通过同一条连接给服务器发消息。这意味着你不能用 SSE 做聊天室、多人协作白板这类需要双向高频交互的应用。要双向,还是得用 WebSocket,别硬用 SSE。

另一个隐蔽的坑在网络代理的缓冲问题。很多企业级代理/Nginx 默认会缓冲 HTTP 响应,等积累到一定量才转发给客户端——这在普通响应时没问题,但对 SSE 来说,等于传送带被中间人囤货了,前端迟迟收到不到 data 消息。解决方法是在 Nginx 中显式关闭缓冲:proxy_buffering off;。这提醒我们:SSE 依赖“边生成边推送”,任何环节的缓冲都可能导致体验毁于一旦。

最后,说一下断线重连策略和心跳机制。浏览器 EventSource 自带自动重连,但服务器端需要配合:客户端重连时,服务器应该知道它断到了哪条消息,这靠 SSE 的 id: 字段实现。同时,如果连接长期空闲(比如大模型在思考),某些中间设备可能把空闲连接当废连接掐掉,所以需要给连接发送心跳注释(:\n\n),告诉代理“我还在活着呢”。这跟寿司师傅为防止客人离席,时不时往传送带上放一碟姜片传递“营业中”信号是一个道理。

要在生产环境用好 SSE,核心原则就是尊重传输路径上的每一个环节。代理要关缓冲,负载均衡要支持长连接超时配置,服务端要发心跳;只有把这些螺丝都拧紧,传送带才能昼夜不息地旋转。

心跳保活=Periodic comment frame (" :nn")\text{心跳保活} = \text{Periodic comment frame} \ (\text{" :\\n\\n"})
公式通俗说:每过一段时间发一条不携带数据的注释消息(以冒号开头),作用是告诉网络设备“连接还活着,别掐断”。
  1. 周期心跳服务端每隔 15-30 秒发送一条注释帧,维持连接活跃。
    \text{:\n\n}
  2. 断线续传客户端重连时携带 Last-Event-ID,服务端据此跳过已发送的消息。
    Last-Event-ID: 123\text{Last-Event-ID: 123}
  3. 代理透传关闭反向代理的响应缓冲,确保数据立刻直达客户端。
    proxy_buffering off;\text{proxy\_buffering off;}

AI 应用场景

场景一:大模型对话应用的逐字输出

阶段:模型推理与服务部署

解决的问题:大模型生成完整回答需要 5-15 秒,传统同步 HTTP 接口让用户长时间面对空白页面。用户被迫在“等待完成”的焦虑中煎熬,极易误以为服务已崩溃。SSE 流式接口将模型输出的 token 实时推送,用户只需等待 0.5 秒就看到第一个字,完整回答分秒之间就位,交互体验越趋近于人与人的即时对话。

具体案例:某客服机器人团队在将大模型接入网页端时,最初使用同步 HTTP 接口,用户反馈“页面像卡死了”。改用 SSE 流式响应后,回答逐字显示,用户等待焦虑大幅缓解,任务完成率提升 31%。实现中后端用 FastAPI 的 StreamingResponse 包装生成器函数,前端使用 fetch API 的 stream 模式读取数据,一行行解析、追加到对话气泡中。

场景二:AI 文档处理任务的实时进度上报

阶段:数据处理与任务编排

解决的问题:在批量处理任务(如文档解析、语音转写、批量润色)中,任务可能耗时数十秒甚至数分钟。传统做法是前端不断轮询任务状态接口,既浪费请求资源,又无法反馈细粒度进度。SSE 单向推送机制恰好匹配“服务器主动上报状态”的需求。

具体案例:某知识库系统管理员上传 50 份 PDF 进行向量化入库,后端通过 SSE 在每完成一份文档时推送一条进度消息(如 data: {"completed": 12, "total": 50}),前端实时渲染进度条,并在所有任务完成时推送 data: {"status": "done"} 触发页面跳转。相比轮询方案,后端请求量降低了 90%。

Python 演示

python46 行
import jsonimport timefrom fastapi import FastAPIfrom fastapi.responses import StreamingResponse app = FastAPI()  def mock_llm_stream(prompt: str):    """模拟大模型流式生成, 按 token 逐个产出回答片段"""    full_answer = f"你好, 你刚才问的是: {prompt}。让我为你详细解答..."    for char in full_answer:        time.sleep(0.05)  # 模拟 50ms 生成一个字        yield char  def format_sse(data: str) -> str:    """将数据封装成 SSE 格式"""    return f"data: {data}\n\n"  @app.get("/chat/stream")def chat_stream(prompt: str):    """SSE 流式对话接口"""    generator = mock_llm_stream(prompt)     def event_stream():        for chunk in generator:            payload = json.dumps({"token": chunk}, ensure_ascii=False)            yield format_sse(payload)     return StreamingResponse(        event_stream(),        media_type="text/event-stream",        headers={            "Cache-Control": "no-cache",            "Connection": "keep-alive",            "X-Accel-Buffering": "no",        },    )  if __name__ == "__main__":    import uvicorn     uvicorn.run(app, host="0.0.0.0", port=8000)
目的:实现一个最小可运行的 SSE 流式接口——客户端访问 /chat/stream?prompt=你好 时,服务端按字逐字推送响应,模拟大模型的打字机效果。
  1. mock_llm_stream(prompt)模拟大模型推理输出流,先拼出完整句子再用 time.sleep(0.05) 逐字吐出,真实场景中这里是调用 vLLM/OpenAI 的流式接口。
  2. format_sse(data)SSE 格式的封装函数,核心是 data: 内容\n\n 格式。这是整个协议的字面核心,前端依赖空行识别一条完整消息。
  3. event_stream()逐字取出生成器内容,包装为 JSON 格式后按 SSE 格式产出。
  4. StreamingResponse(...)FastAPI 的关键封装:接收生成器对象并保持 HTTP 连接不断开,返回流式响应。
  5. media_type="text/event-stream"设置 SSE 特有的 MIME 类型,浏览器和前端库据此知道该用流式方式解析。
  6. "Cache-Control": "no-cache"禁用缓存,防止代理或浏览器缓存 SSE 响应导致数据延误。
  7. "X-Accel-Buffering": "no"专门针对 Nginx 的响应头,关闭代理缓冲,让数据立即传送,不被囤积。

常见误区

误区既然 SSE 是一条“连接”,那客户端应该也能通过它向服务器发送数据吧?就像 WebSocket 一样,来回传递消息。

正解SSE 是单向通道,数据只能从服务器流向客户端。在浏览器中,EventSource API 根本没有提供向服务器发送消息的方法。如果需要双向通信,WebSocket 才是正确的工具——这就好比传送带的设计目标是把菜从厨房送到客人的面前,而不是让客人把菜传回厨房。

为何常见:很多人看到“持久连接”就自动脑补出“双向会话”,因为市面上主流的持久连接方案(WebSocket、Socket.IO)几乎都是双向的,容易以偏概全。

误区SSE 应该是像 WebSocket 一样的新传输协议,需要专用的服务器、专用的端口和复杂的握手流程才能使用。

正解SSE 不是新协议,它仍然构建在普通 HTTP 之上,只是通过 Content-Type: text/event-stream 响应头和 Transfer-Encoding: chunked 或逐块刷新机制,让连接在响应完成后继续存活。这意味着任何支持 HTTP 的服务器都能实现 SSE,不需要升级到新协议栈,防火墙和代理通常也无需额外放行。它就像走的还是普通马路,只是司机不停车而已。

为何常见:WebSocket 的“升级协议”概念太深入人心,导致人们想当然地认为所有实时通信方案都要有新协议。

误区既然 SSE 消息格式是 `data: 文本`,那它一定只能传纯文本,不能传 JSON 或二进制数据。

正解SSE 的 data: 字段里穿什么内容完全由你决定。实际生产中,最常见的做法恰恰是把 JSON 序列化成字符串放在 data: 后传输——比如 data: {"token":"你", "index":0}。只要在服务端序列化、在客户端解析,就能传递结构化数据。二进制内容可以转成 Base64 编码再传,只是传输效率不如 WebSocket 的原生二进制帧。把它想成传送带上的餐盒,里面装生鱼片你可以把盒盖打开直接吃,但你不影响装便当盒里还有米饭和味噌汤。

为何常见:看到“文本/事件流”(text/event-stream)就陷入字面理解的误区,忽略了底层可以承载任意序列化数据的事实。

误区SSE 的持久连接既然一直不关闭,那它肯定比普通请求更消耗服务器资源,连接数一多服务器就垮了。

正解SSE 连接确实长期占用,但一个空闲的 SSE 连接几乎不消耗 CPU,只保留一个 socket 文件描述符和少量内存。相比 WebSocket 需要维护双向心跳和额外状态,SSE 在空闲时的开销更低。现代服务器(如 Nginx 或 Go)处理十万级并发连接早已是常态。真正需要关注的是代理服务器的连接数限制,而不是连接本身的成本。

为何常见:把“长连接”和“高资源消耗”划等号——对网络编程不熟悉的人容易高估 socket 占用的成本。

面试题

Q1. SSE 的全称和核心特征是什么?

单选题 · mid

  • A.Server-Sent Events,基于 HTTP 的单向服务器推送协议← 正确答案
  • B.Server-Socket Events,基于 TCP 的双向通信协议
  • C.Standard-Sent Events,基于 UDP 的广播协议
  • D.Stream-Socket Events,基于 WebSocket 的推送协议

答案:A。SSE 是 Server-Sent Events 的缩写,它仍然基于标准 HTTP 协议,数据流只能从服务器流向客户端。B 提到“双向”与 SSE 本质矛盾;C 错在 UDP——SSE 是 TCP 之上的 HTTP;D 错在把 SSE 和 WebSocket 混为一谈,两者是完全不同的机制。

Q2. SSE 消息的标准格式是什么?

单选题 · easy

  • A.data: 内容\n(单换行结尾)
  • B.data: 内容\n\n(双换行结尾)← 正确答案
  • C.stream: 内容\n\n
  • D.push: 内容\n\n

答案:B。SSE 规范规定消息字段以单换行结束,消息之间用空行(即双换行)分隔。data: 内容\n\n 是标准的单条消息格式,前面的 \n 是字段行结束符,后面的 \n 是消息结束的空行标记。A 少了空行标记,C 和 D 字段名错误——SSE 没有 stream: 或 push: 字段。

Q3. SSE 默认使用哪种 MIME 类型标识流式响应?

单选题 · easy

  • A.application/json
  • B.text/plain
  • C.text/event-stream← 正确答案
  • D.application/octet-stream

答案:C。这是 SSE 的标识性特征。服务器必须把 Content-Type 设置为 text/event-stream,浏览器才能识别这是 SSE 连接并按事件流格式解析。application/json 是普通 JSON 响应,text/plain 是纯文本,application/octet-stream 是二进制流——三者都是普通一次性响应。

Q4. SSE 与 WebSocket 相比,最大的结构性区别是什么?

单选题 · mid

  • A.SSE 基于 UDP,WebSocket 基于 TCP
  • B.SSE 是单向的,WebSocket 是双向的← 正确答案
  • C.SSE 只支持二进制数据,WebSocket 只支持文本数据
  • D.SSE 必须使用多个连接,WebSocket 只能使用一个

答案:B。SSE 是服务器到客户端的单向通道,WebSocket 则允许客户端和服务器双向收发消息。A 错误——两者都基于 TCP;C 错误——SSE 传文本为主但可封装 JSON 字符串,WebSocket 两者都支持;D 错误——SSE 只需一个连接,逻辑上反向消息通过普通 HTTP 另发。

Q5. 在浏览器中接收 SSE 事件流,最合适的原生 API 是什么?

单选题 · mid

  • A.WebSocket
  • B.fetch + 手动解析流
  • C.XMLHttpRequest
  • D.EventSource← 正确答案

答案:D。EventSource 是浏览器原生提供、专为 SSE 设计的 API,它内置了连接管理、自动重连、消息事件派发等功能。WebSocket 是另一套协议和 API;fetch 的 stream 模式需要手动处理 SSE 格式,是备选方案(服务端代理场景常用);XMLHttpRequest 对长连接支持差,不是正确选项。

Q6. 下列哪些场景适合使用 SSE 而不是 WebSocket?

多选题 · mid

  • A.大模型对话应用逐字输出回答← 正确答案
  • B.实时双向联机游戏(如多人棋牌)
  • C.服务器向客户端推送实时进度通知← 正确答案
  • D.视频通话的音视频双向传输

答案:AC。SSE 适合“服务器单方面持续推数据”的场景。大模型输出(A)和进度上报(C)都是典型单向推送,SSE 足够且更轻。B 和 D 需要客户端和服务器高频双向交互——联机游戏要同步双方操作,视频通话要双向传音视频流,这些必须用 WebSocket 或其他实时双向协议。

Q7. 服务端用 FastAPI 实现 SSE 流式响应时,哪个关键步骤是必须的?

单选题 · extreme

  • A.使用 WebSocket 装饰器路由
  • B.使用生成器逐块产出内容,并设置 media_type="text/event-stream"← 正确答案
  • C.设置 Content-Encoding: gzip 压缩头
  • D.设置 Accept-Encoding 请求头

答案:B。FastAPI 的 StreamingResponse 接收生成器对象并持续输出,配合 media_type="text/event-stream" 让响应变为 SSE 流。A 是 WebSocket 的实现方式;C 的 gzip 在流式场景反而会因缓冲破坏实时性;D 是请求头不是响应头,不在此处设置。

Q8. SSE 连接的自动重连特性,主要依赖浏览器端的什么机制?

单选题 · mid

  • A.setInterval 定时器主动轮询服务器
  • B.EventSource API 对断连的自动重试← 正确答案
  • C.HTTP 协议本身的重传机制
  • D.用户手动刷新页面

答案:B。EventSource 内置了连接中断后自动重新建立连接的能力,重试间隔约 2-3 秒(可配置),无需开发者额外编写重连逻辑。A 是轮询方案,不是 SSE 的机制;C 的 HTTP 重传只针对普通响应,不覆盖连接中断后的重新建立;D 把体验交给用户,不符合“内置重连”的特性。

Q9. SSE 在实际部署中可能遇到哪些坑?

多选题 · mid

  • A.Nginx 等反向代理缓冲导致数据延迟送达← 正确答案
  • B.连接空闲超时被中间设备掐断← 正确答案
  • C.代理缓存错误造成客户端收到旧数据← 正确答案
  • D.SSE 消息格式错误导致客户端无法解析← 正确答案

答案:ABCD。这四个都是真实的部署问题。A 需设置 proxy_buffering off 关闭缓冲;B 需通过心跳注释保持连接活跃;C 需设置 Cache-Control: no-cache 并让所有中间代理不缓存响应;D 是协议层问题,必须保证每条消息以空行正确结尾,以及 data: 前缀的拼写无误。

Q10. SSE 的 id: 字段在消息中的作用是什么?

单选题 · easy

  • A.标记消息的发送时间
  • B.标记消息的唯一标识,用于断线重连时续传← 正确答案
  • C.标记消息的优先级
  • D.标记消息的语言种类

答案:B。id: 字段给消息分配唯一标识。连接断开后,EventSource 重连时会自动带上 Last-Event-ID 请求头,服务器据此判断客户端已经收到哪条消息,从下一条继续发送。这个设计和断点续传的机制密切相关,是 SSE 在弱网环境下保持可靠性的关键工具。