AI 大模型爆火的 SSE 技术到底是什么?万字长文,一篇读懂 SSE!

本文由 45 岁老架构师尼恩分享,感谢作者,有修订和重新排版。
1、引言
你有没有想过,为什么 ChatGPT 的回答能逐字逐句地“流”出来?这一切的背后,都离不开一项关键技术——SSE(Server-Sent Events)!
本文从 SSE(Server-Sent Events)技术的原理到示例代码,为你通俗易懂的讲解 SSE 技术的方方面面。
2、AI 大模型实时通信技术专题
技术专题系列文章目录如下,本文是第 4 篇:
《全民 AI 时代,大模型客户端和服务端的实时通信到底用什么协议?》
《大模型时代多模型 AI 网关的架构设计与实现》
《通俗易懂:AI 大模型基于 SSE 的实时流式响应技术原理和实践示例》
《ChatGPT 如何实现聊天一样的实时交互?快速读懂 SSE 实时“推”技术 》
《AI 大模型爆火的 SSE 技术到底是什么?万字长文,一篇读懂 SSE! 》(☜ 本文)
3、初识 SSE
SSE(Server-Sent Events)是一种基于 HTTP 协议的服务器推送技术,允许服务端主动向客户端发送数据流。
SSE 可以被理解为 HTTP 的一个扩展或一种特定用法。它不是一个全新的、独立的协议,而是构建在标准 HTTP/1.1 协议之上的技术。
SSE 就像是服务器打开了一个“单向数据管道”,服务器通过 HTTP 扩展 可以持续不断地流向浏览器,无需客户端反复发起请求。其实很简单的: SSE = HTTP 扩展字段 + Keepalive 长连接。
SSE 提供了一种简单、可靠的方式来实现服务器向客户端的实时数据推送。它非常适合通知、实时数据更新、日志流和类似 ChatGPT 的逐字输出场景。如果你只需要单向通信,SSE 往往是比 WebSocket 更简单、更轻量的选择。
SSE 适用于服务器主动向客户端推送数据的场景,如实时通知、动态更新等。
所以,目前 几乎所有主流浏览器都原生支持 SSE。
PS:更详细的 SSE 技术资料,可以进一步阅读以下几篇:
Web 端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
SSE 技术详解:一种全新的 HTML5 服务器推送事件技术详解
Web 端通信方式的演进:从 Ajax、JSONP 到 SSE、Websocket
一文读懂前端技术演进:盘点 Web 前端 20 年的技术变迁史
网页端 IM 通信技术快速入门:短轮询、长轮询、SSE、WebSocket
搞懂现代 Web 端即时通讯技术一文就够:WebSocket、socket.io、SSE
4、SSE 的诞生背景
4.1 短轮询、长轮询、Flash 、 WebSocket
在 SSE 技术出现之前,Web 应用要实现服务器向客户端的实时数据推送,主要依赖以下几种技术,但它们都存在明显的缺陷。
4.1.1)短轮询 (Polling):
原理:用短连接请求数据。客户端以固定的时间间隔(例如每秒一次)频繁地向服务器发送请求,询问是否有新数据。
缺点:大量请求可能是无效的(无新数据),浪费服务器和带宽资源,实时性差。
短轮询的技术流程图:
4.1.2)长轮询 (Long Polling):
原理:使用长连接请求数据。 客户端发送一个请求,服务器会保持这个连接打开(长连接),直到有新数据可用或超时。一旦客户端收到响应,会立即发起下一个请求。
缺点:虽然减少了无效请求,但每个连接仍然需要客户端发起,服务器需要维护大量挂起的连接,实现复杂。
长轮询 (Long Polling) 的技术突破:减少无效请求,但服务器需维护挂起连接
4.1.3)基于 Flash 的解决方案:
原理:利用 Adobe Flash 插件提供的 Socket 功能实现全双工通信。
缺点:依赖浏览器插件,在移动端(如 iPhone)不受支持,且随着技术的发展(Flash 被淘汰)已走向消亡。基于 Flash 方法都非原生支持,效率低下或依赖外部插件。
4.1.4)基于 WebSocket 的解决方案:
原理:在客户端与服务器之间建立一条全双工的 TCP 长连接,双方可随时互相推送数据。
缺点:
1)需要一次额外的协议升级握手(Upgrade: websocket),对 CDN、防火墙、代理服务器的兼容性不如普通 HTTP;2)双向通信能力在“服务器→客户端单向推送”场景下显得过度设计,增加心跳、重连、帧解析等复杂度;3)早期浏览器支持不一(IE ≤ 9 无原生实现),需要 Polyfill 或 Flash 降级方案。4
WebSocket 全双工通道的革命性:摆脱 HTTP 束缚,实现真正的实时交互(PS: WebSocket 并不仅是 Web 领域的通讯协议,它属于复杂度较高的二进制通讯协议)。
4.2 SSE 诞生的核心背景因此,Web 领域迫切需要一种标准化的、高效的、由浏览器原生支持的服务器到客户端的单向通信机制。这就是 SSE 诞生的核心背景。
核心需求:
1)简单:易于服务器和客户端实现;2)高效:基于 HTTP/HTTPS,避免不必要的请求开销;3)标准:成为 W3C 标准,得到浏览器原生支持;4)自动重连:内置连接失败后自动重试的机制。SSE——真正的服务器推送:
5、SSE 的前世今生
SSE 的发展是 Web 标准化进程和实时通信需求共同推动的结果。
下图概述了其关键发展节点:
让我们对图中的关键阶段进行详细解读。
1)诞生背景(2006 年以前):
Web 早期只有“请求-响应”范式,实时需求(股票、IM、行情)只能靠轮询或长轮询,延迟高、浪费资源。Comet(长连接 iframe、jsonp、xhr-streaming 等 Hack 方案)出现,但实现复杂、浏览器兼容性差、占用连接数高。
业界急需一种“浏览器原生、基于 HTTP、单向服务器推送”的轻量机制。
2)概念提出与标准化 (约 2006-2009 年):
SSE 的概念最初作为 HTML5 标准的一部分被提出,由 WHATWG (Web Hypertext Application Technology Working Group) 和 W3C (World Wide Web Consortium) 共同推动。
其设计思想是定义一个简单的、基于 HTTP 的协议,允许服务器通过一个长连接持续地向客户端发送文本流。
2006 年,Opera 9 在浏览器里率先实现名为 Server-Sent Events 的实验 API,用 DOM 事件把服务器推送的文本块喂给页面。
同期 WHATWG HTML5 草案开始收录相关章节,定义了 text/event-stream MIME 类型及“event: / data:”行协议。
后来,它从庞大的 HTML5 规范中分离出来,成为了一个独立的 W3C 标准文档。
2008 年,SSE 被正式写入 HTML5 草案,随后进入 W3C 标准流程。
3)浏览器支持与推广 (约 2010-2015 年):
2011 年左右,主流浏览器(如 Firefox、Chrome、Safari、Opera)开始陆续支持 SSE API。 Firefox 6、Chrome 6、Safari 5、Opera 11.5 陆续完成原生实现;IE 系列缺席(直到 Edge 79 才补票)。
关键的障碍:Internet Explorer (包括 IE 11) 始终没有支持 SSE API。这在一定程度上限制了其早期的广泛应用,开发者通常需要为此准备降级方案(如回落到长轮询)。
随着 Chrome、Firefox 等现代浏览器的市场份额不断上升,以及移动端浏览器对 SSE 的良好支持,SSE 逐渐成为开发实时 Web 应用的可信选择。
2014 年 10 月:HTML5 成为 W3C Recommendation,SSE 作为官方子模块锁定最终语法,浏览器阵营格局定型。
4)正式推荐与成熟 (2015 年至 2022 ):
2015-2020 年,WebSocket 与 WebRTC 占据实时通信话题中心,SSE 主要在企业内部仪表盘、日志 tail 等低频场景默默使用。
SSE 由于有 “单向文本流 + 自动重连 + 轻量” 特性,所以没有被 WebSocket 与 WebRTC 踩死, 使其在 IoT 设备、移动端 WebView 中仍保有一席之地。
2015 年,W3C 发布了 Server-Sent Events 的正式推荐标准,标志着该技术的成熟和稳定。在此期间,前端生态框架(如 React、Vue.js)和后端语言(如 Node.js、Python、Java)都提供了对 SSE 的良好支持,出现了大量易用的库和示例。
5) 大模型时代的爆发(2022 至今):
虽然 WebSocket 提供了全双工通信能力,但 SSE 因其简单的 API、基于 HTTP 带来的良好兼容性(如无需担心代理或防火墙问题)、以及自动重连等特性,在只需要服务器向客户端推送数据的场景中(如新闻推送、实时行情、状态更新、AI 处理进度流式输出等)成为了更简单、更合适的选择。
ChatGPT、Claude 等生成式 AI 需要“打字机”式逐 token 输出,SSE 天然契合:
1)基于 HTTP/1.1 无需升级协议,CDN 缓存友好;2)浏览器 EventSource API 一行代码即可接入;3)文本流可直接承载 JSON Lines 或 markdown 片段。2022 年底起:OpenAI、Anthropic、Google Bard 均把 text/event-stream 作为官方流式回答协议,社区库(FastAPI SSE-Star、Spring WebFlux、Node sse.js、Go gin-sse)迎来二次繁荣。
6、SSE 的技术特征
SSE 和 WebSocket 都能建立浏览器与服务器的长期通信,但区别很明显:
1)SSE 是单向推送 不是双向推送, 而且是 http 协议的一个扩展协议, 使用简单、自动重连,适合文本类实时推送;2)WebSocket 是双向通信,不是 http 协议的一个扩展协议,WebSocket 更灵活,但实现相对复杂。8 流程解读:
1)连接初始化:客户端使用特定的 Content-Type: text/event-stream 向服务器发起一个普通的 HTTP GET 请求。服务器确认并保持连接开放。2)数据推送:服务器通过保持打开的连接,以纯文本格式(遵循 data: ...、event: ... 等规范)持续发送数据块。每个消息以两个换行符 \n\n 结束。3)连接容错:如果连接因网络问题中断,SSE 客户端内置的机制会自动尝试重新建立连接,极大地提高了应用的鲁棒性。4)客户端处理:浏览器端的 EventSource API 会解析收到的数据流,触发相应的事件(如 onmessage 或自定义事件),让开发者能够处理推送来的数据。SSE 的诞生是 Web 开发对简单、高效、标准化的服务器推送技术需求的直接结果。它有效地替代了笨拙的轮询技术,在与 WebSocket 的竞争中,找到了自身在单向数据流场景下的独特定位。
其发展历程经历了从概念提出、浏览器支持到成为正式标准的完整路径。尽管曾受限于 IE,但在现代浏览器中已成为一项稳定、可靠且被广泛采用的技术。如今,在实时通知、金融仪表盘、实时日志跟踪和大型语言模型(LLM)的流式响应输出等场景中,SSE 都是首选的解决方案。
7、默默无闻的 SSE 为何在 AI 大模型时代一夜爆火?
SSE 最近站到聚光灯下,几乎可以说最大的推手就是当前 AI 应用(尤其是 ChatGPT 等大型语言模型)的爆发式增长。SSE 之所以成为 AI 应用的“标配”,是因为 SSE 与 AI 所需的“打字机” 输出模式 是 天作之合。
7.1 什么是 AI 大模型“打字机” 式的逐 token 输出?
“打字机”式 逐 token 输出是一种流式传输方式,它模拟了人类打字或思考的过程。
服务器不是等待 LLM 生成整个答案 后一次性发送给 用户,而是 流式输出, 每生成一个“词元”(token,可以粗略理解为一个词或一个字),就立刻发送这个“词元”。
下面举一个例子,对比 一下 传统方式(非流式)和 “打字机” (流式)式 的过程。
传统方式(非流式)过程如下:
1)你提问:“请写一首关于春天的诗”。2)服务器端的 AI 开始思考、生成,整个过程你需要等待(可能好几秒甚至更久)。3)AI 生成完整的诗歌:“春风拂面绿意浓,百花争艳映晴空...”。4)服务器将整首诗作为一个完整的 JSON 对象 { "content": "春风拂面绿意浓,百花争艳映晴空..." } 发送给客户端。5)客户端一次性收到全部内容并渲染出来。“打字机”(流式)过程如下:
1)你提问:“请写一首关于春天的诗”。2)服务器端的 AI 生成第一个 token “春”,立刻通过 SSE 发送 data: “春”。3)客户端收到“春”并显示出来。4)AI 生成第二个 token “风”,立刻发送 data: “风”。5)客户端在“春”后面追加“风”,形成“春风”。6)后续 token “拂”、“面”、“绿”、“意”、“浓”... 依次迅速发送和追加。7)你看到的效果就是文字一个接一个地“打”在屏幕上,就像有人在远端为你实时打字一样。“打字机”(流式) 模式的巨大优势:
1)极低的感知延迟:用户几乎在提问后瞬间就能看到第一个字开始输出,无需经历漫长的等待白屏期,体验流畅自然。2)提供了“正在进行”的反馈:看着文字逐个出现,给人一种模型正在为你“思考”和“创作”的生动感,而不是在“沉默中宕机”。3)更高效地利用时间:用户可以在前半句还在输出时,就开始阅读和理解,节省了总体的认知时间。7.2 为什么 SSE 跟 AI 大模型是“天作之合”?这正是 SSE 的设计初衷和核心优势所在,它与 AI 流式输出的需求完美匹配。
1)单向通信的完美匹配:
AI 的文本生成过程本质上是服务器到客户端的单向数据推送。客户端只需要接收,不需要在生成过程中频繁地发送请求。SSE 的“服务器推送”模型正是为此而生,而 WebSocket 的双向能力在这里是多余的。
2)基于 HTTP/HTTPS,简单且兼容:
SSE 使用标准的 HTTP 协议,这意味着 SSE 易于实现和调试:任何后端框架和前端语言都能轻松处理。在浏览器中调试时,你可以在“网络”选项卡中直接看到以文本流形式传输的事件,非常直观。
SSE 使用标准的 HTTP 协议,这还意味着 容易绕过网络障碍:公司防火墙和代理通常对 HTTP/HTTPS 放行,而可能会阻拦陌生的 WebSocket 协议。这使得 SSE 的部署兼容性极好。
3)内置的自动重连机制:
网络连接并不完全可靠。如果用户在接收很长的回答时网络波动,连接中断,SSE 客户端会自动尝试重新连接。这对于长时间流的应用至关重要,提供了天然的鲁棒性。
4)轻量级的文本协议:
AI 流式输出传输的就是文本(UTF-8 编码)。SSE 的协议 data: ...\n\n 就是为传输文本片段而设计的,极其高效和简单。WebSocket 虽然也能传文本,但其协议设计还考虑了二进制帧、掩码等更复杂的情况,对于纯文本流来说显得有些“重”。
5)原生浏览器 API:
现代浏览器都原生支持 EventSource API,开发者无需引入额外的第三方库,即可轻松实现接收流式数据,减少了依赖和打包体积。
所以,SSE 站到聚光灯下的原因正是:
AI 应用需要“打字机”式的逐 token 输出体验,而 SSE 作为一种基于 HTTP 的、简单的、单向的服务器推送技术,是实现这种体验最自然、最高效、最可靠的技术选择。
它就像是为这个场景量身定做的工具,没有多余的功能,只有恰到好处的设计。因此,当 ChatGPT 等应用席卷全球时,其背后默默无闻的 SSE 技术也终于从幕后走到了台前,被广大开发者所重新认识和重视。
8、SSE 的技术原理详解
8.1 工作机制的流程图 SSE 通过一个持久的 HTTP 连接实现服务器到客户端的单向数据流。
以下是其工作机制的流程图:
以下是关键步骤解析。
1)浏览器发起一个 HTTP 请求,Header 中包含:
Accept: text/event-stream
2)服务器响应类型必须为:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
3)服务器发送事件格式(每个事件以两个换行符结束):
event: message
data: {"time": "2023-10-05T12:00:00", "value": "New update!"}
id: 12345
retry: 5000
\n\n
4)浏览器通过 EventSourceAPI 接收并处理事件。
5)服务器发送 一个特殊“结束”事件,可以结束传输。比如,服务器发送一个如 event: end 的消息,可以结束传输。客户端预先监听这个自定义的 end 事件,一旦收到,就知道传输结束,并可以选择主动关闭 EventSource 连接。
6)若连接中断,浏览器会根据 retry 字段自动重连。如果没有收到 特殊“结束”事件, 浏览器 可以自动重连。
8.2 SSE 与其他通信方式对比不同通信技术各有适用场景,我们用表格清晰对比:
8.3 SSE 的适用场景 1)ChatGPT 式逐字输出( “打字机” 式逐 词元 token 输出):
2)实时通知系统:
a. 新订单提醒;b. 用户消息推送;c. 审核状态更新。3)实时数据看板:
a. 股票行情;b. 设备监控数据;c. 实时日志流。9、SSE 客户端 API 详解 SSE 的客户端实现非常简单,浏览器原生提供了 EventSource 对象来处理与服务器的 SSE 连接。下面我们详细介绍它的使用方法和核心特性。
9.1 认识浏览器端 EventSource 对象
浏览器兼容性检测:
在使用 SSE 前,首先需要确认当前浏览器是否支持 EventSource(除 IE/Edge 外,几乎所有现代浏览器都支持)。
检测方法如下:
// 检查浏览器是否支持 SSE
if ('EventSource' in window) {
// 支持 SSE,可正常使用
console.log('浏览器支持 SSE');
} else {
// 不支持 SSE,需降级处理
console.log('浏览器不支持 SSE');
}
创建连接:
使用 EventSource 创建与服务器的连接非常简单,只需传入服务器的 SSE 接口地址:
// 建立与服务器的 SSE 连接
// url 为服务器提供的 SSE 接口地址(可同域或跨域)
var source = new EventSource(url);
如果需要跨域请求并携带 Cookie,可通过第二个参数配置:
// 跨域请求时,允许携带 Cookie
var source = new EventSource(url, {
withCredentials: true // 默认为 false,设为 true 表示跨域请求携带 Cookie
});
连接状态(readyState):
EventSource 实例的 readyState 属性用于表示当前连接状态,只读且有三个可能值:
可以通过该属性判断当前连接状态,例如:
if (source.readyState === EventSource.OPEN) {
console.log('SSE 连接已正常建立');
}
9.2 基本使用方法 EventSource 通过事件机制处理连接过程中的各种状态和接收的数据,核心事件包括 open、message、error。
下面用流程图展示 SSE 客户端的完整使用流程:
连接建立:open 事件
当客户端与服务器成功建立 SSE 连接时,会触发 open 事件:
// 方式 1:使用 onopen 属性
source.onopen = function (event) {
console.log('SSE 连接已建立');
// 可在此处做连接成功后的初始化操作,如更新 UI 状态
};
// 方式 2:使用 addEventListener(推荐,可添加多个回调)
source.addEventListener('open', function (event) {
console.log('SSE 连接已建立(监听方式)');
}, false);
接收数据:message 事件
当客户端收到服务器推送的数据时,会触发 message 事件(默认事件,处理未指定类型的消息):
// 方式 1:使用 onmessage 属性
source.onmessage = function (event) {
// event.data 为服务器推送的文本数据
var data = event.data;
console.log('收到数据:', data);
// 可在此处处理数据,如更新页面内容
};
// 方式 2:使用 addEventListener
source.addEventListener('message', function (event) {
var data = event.data;
console.log('收到数据(监听方式):', data);
}, false);
注意:event.data 始终是字符串类型,如果服务器发送的是 JSON 数据,需要用 JSON.parse(data)转换。
连接错误:error 事件
当连接发生错误(如网络中断、服务器出错)时,会触发 error 事件:
// 方式 1:使用 onerror 属性
source.onerror = function (event) {
// 可根据 readyState 判断错误类型
if (source.readyState === EventSource.CONNECTING) {
} else {
}
};
// 方式 2:使用 addEventListener
source.addEventListener('error', function (event) {
// 错误处理逻辑
}, false);
关闭连接:close()方法
如果需要主动关闭 SSE 连接(关闭后不会自动重连),可调用 close()方法:
// 主动关闭 SSE 连接
source.close();
console.log('SSE 连接已手动关闭');
9.3 自定义事件默认情况下,服务器推送的消息会触发 message 事件。但实际开发中,我们可能需要区分不同类型的消息(如"新订单通知"和"系统公告"),这时就可以使用自定义事件。
客户端通过 addEventListener 监听自定义事件名,例如监听 order 事件:
// 监听名为"order"的自定义事件
source.addEventListener('order', function (event) {
var orderData = event.data;
console.log('收到新订单:', orderData);
// 处理订单相关逻辑
}, false);
// 再监听一个名为"notice"的自定义事件
source.addEventListener('notice', function (event) {
var noticeData = event.data;
console.log('收到系统公告:', noticeData);
// 处理公告相关逻辑
}, false);
注意:自定义事件不会触发 message 事件,只会被对应的 addEventListener 捕获。
上面代码中,浏览器对 SSE 的 foonotice事件进行监听。如何实现服务器发送foonotice 事件,请看下文。
10、SSE 服务器端技术详解
服务器要实现 SSE,核心是按照特定格式向客户端发送数据。下面详细介绍服务器端的实现规范。
10.1 HTTP 头信息要求服务器向客户端发送 SSE 数据时,必须设置以下 HTTP 响应头,否则客户端无法正确识别为事件流:
Content-Type: text/event-stream // 必须,指定为事件流类型
Cache-Control: no-cache // 必须,禁止缓存,确保数据实时性
Connection: keep-alive // 必须,保持长连接
这三个头信息是 SSE 的基础,缺少任何一个都可能导致连接失败或数据异常。
10.2 数据传输格式
服务器发送的每条消息(message)由多行组成,每行格式为[字段]: 值\n(字段名后必须跟冒号和空格,结尾用换行符\n)。多条消息之间用\n\n(两个换行符)分隔。
此外,以 : 开头的行是注释(服务器可定期发送注释保持连接)。
基本格式示例:
: 这是一条注释(客户端会忽略)\n
data: 这是第一条消息\n\n
data: 这是第二条消息的第一行\n
data: 这是第二条消息的第二行\n\n
注意:换行符必须是\n(Unix 格式),\r\n 可能导致客户端解析错误。
10.3 核心字段说明 SSE 消息支持四个核心字段,分别用于不同场景。
1)data 字段:消息内容:
data 字段用于携带实际的消息内容,是最常用的字段。
单行数据:
data: Hello, SSE!\n\n // 单行数据,以\n\n 结束
多行数据(适合 JSON 等复杂结构):
data: {\n // 第一行以\n 结束
data: "name": "张三",\n // 第二行以\n 结束
data: "age": 20\n // 第三行以\n 结束
data: }\n\n // 最后一行以\n\n 结束
客户端接收后,event.data 会自动拼接为完整字符串:{"name": "张三","age": 20}
2)event 字段:指定事件类型:
event 字段用于指定消息的事件类型,客户端可通过对应事件名监听(即 9.3 节的自定义事件)。
服务器发送:
event: order\n // 指定事件类型为 order
data: 新订单 ID:12345\n // 消息内容
\n // 消息结束(\n\n 简化为单独一行)
客户端监听:
source.addEventListener('order', function(event) {
console.log(event.data); // 输出:新订单 ID:12345
});
3)id 字段:消息标识:
id 字段用于给消息设置唯一标识,客户端会自动记录最后一条消息的 id(存于 source.lastEventId)。
核心作用:当连接断线重连时,客户端会在请求头中携带 Last-Event-ID: [最后收到的 id],服务器可根据该 ID 恢复数据传输(避免重复或丢失)。
服务器发送:
id: msg1001\n // 消息标识
data: 这是第 1001 条消息\n
\n
客户端重连时的请求头:
Last-Event-ID: msg1001 // 自动携带最后收到的 id
4)retry 字段:重连间隔:
retry 字段用于指定客户端断线后的重连间隔(单位:毫秒),默认重连间隔约为 3 秒。
服务器发送:
retry: 5000\n // 告诉客户端,断线后 5 秒再重连
data: 重连间隔已设置为 5 秒\n
\n
5)服务器保持连接示例:
服务器可以定期发送注释行,保持连接活跃:
: 这是保持连接活动的注释行\n
: 服务器时间 2023-10-05T12:00:00\n
10.4 服务器发送流程服务器发送 SSE 数据的完整流程如下:
15
下面是一个包含多种字段的服务器发送示例,模拟一个实时通知系统:
: 服务器开始发送消息(注释)\n
id: 1001\n
event: notice\n
data: 系统将在 10 分钟后维护\n\n
id: 1002\n
event: order\n
data: {"orderId": "20230501", "status": "paid"}\n\n
retry: 10000\n
id: 1003\n
data: 重连间隔已调整为 10 秒\n\n
: 这是保持连接活动的注释行\n
: 服务器时间 2023-10-05T12:00:00\n
客户端接收后:
1)notice 事件会捕获到"系统将在 10 分钟后维护"2)order 事件会捕获到订单 JSON 数据 3)重连间隔被设置为 10 秒 4)最后收到的消息 ID 是 1003(断线重连时会携带)通过以上规范,服务器就能轻松实现 SSE 功能,向客户端实时推送数据。相比 WebSocket,SSE 的服务器实现更简单,无需处理复杂的协议握手,只需按格式发送文本数据即可。
11、SSE 实战代码示例(基于 Spring Boot 的实时通信)接下来, 通过一个完整案例 手把手教你用 Spring Boot 实现 SSE 功能。这个案例包含服务端(后端)和客户端(前端)代码, 可以直接运行体验服务器主动推送数据的效果。
11.1 案例整体架构我们要实现的系统包含三个核心部分:
1)后端服务:基于 Spring Boot,提供 SSE 连接接口、消息广播接口和任务进度推送接口;2)前端页面:一个简单的 HTML 页面,通过 EventSource 与后端建立 SSE 连接;3)交互流程:客户端连接后,可接收服务器主动推送的连接状态、广播消息和任务进度。整体架构流程图:
16
11.2 服务端实现 11.2.1)准备依赖:
首先创建 Spring Boot 项目,在 pom.xml 中添加以下依赖(用于 web 开发和页面渲染):
<dependencies>
</dependencies>
这些依赖是基础:spring-boot-starter-web 提供了 SSE 核心类 SseEmitter,spring-boot-starter-thymeleaf 用于将 HTML 页面返回给浏览器。
11.2.2)编写 SSE 核心控制器:
创建 SseController,这是服务端处理 SSE 连接和消息推送的核心类:
package com.example.sse.controller;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
public class SseController {
/
/
}
核心代码说明:
1)SseEmitter:Spring 提供的 SSE 核心类,每个实例对应一个客户端连接;2)emitters 列表:管理所有活跃连接,方便广播消息(类似"客户端注册表");3)executor 线程池:异步处理消息发送,避免阻塞主线程(如果同步发送,一个客户端卡住会影响所有用户)。事件发送:通过 emitter.send(SseEmitter.event()) 构建消息,可指定事件名、数据、ID 和重连时间。
11.2.3)编写页面控制器:
创建 PageController,用于将前端页面返回给浏览器:
package com.example.sse.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller // 注意这里用 @Controller 而非 @RestController,用于返回页面
public class PageController {
/
}
11.3 客户端实现(HTML 页面)在 src/main/resources/templates 目录下创建 sse-client.html,这是用户交互的前端页面:
Server-Sent Events (SSE) Client Connect to SSE Disconnect Send Broadcast Start Task Messages:
客户端核心逻辑:
1)eventSource:EventSource 实例,是客户端与服务端 SSE 连接的"桥梁";2)事件监听:通过 addEventListener 监听服务端定义的事件(CONNECTED/BROADCAST 等);3)自动重连:当连接断开时,EventSource 会自动重试(无需手动写重连逻辑);4)交互函数:connectSSE/disconnectSSE 等函数对应页面按钮,实现用户操作。11.4 运行与测试启动步骤:
1)确保 Spring Boot 项目配置正确(默认端口 8080,无需额外配置);2)启动 Spring Boot 应用(运行带有 main 方法的启动类);3)打开浏览器,访问 http://localhost:8080/,看到客户端页面。功能测试:
1)建立连接:点击"Connect to SSE"按钮,页面会显示"连接成功"的消息(服务端通过 CONNECTED 事件推送)2)发送广播:点击"Send Broadcast"按钮,输入任意消息(如"Hello SSE"),页面会显示广播消息(服务端向所有连接的客户端推送)3)启动任务:点击"Start Task"按钮,页面会每秒收到一条进度消息(从 0%到 100%),最后显示"任务完成"4)断开连接:点击"Disconnect"按钮,连接关闭,不再接收消息。测试流程图:
17
11.5 服务端的关键技术点 1)SseEmitter 的作用:Spring 封装的 SSE 工具类,简化了"保持连接+发送事件"的实现,无需手动处理 HTTP 流格式。
2)连接管理:用 CopyOnWriteArrayList 存储活跃连接,确保线程安全;通过 onCompletion/onTimeout 回调清理无效连接,避免内存泄漏。
3)异步处理:必须用线程池(ExecutorService)异步发送消息,否则会阻塞主线程,导致新请求无法处理。
4)事件设计:通过 name 区分不同类型的事件(如 PROGRESS/BROADCAST),客户端按需监听,逻辑更清晰。
5)自动重连:SSE 客户端(EventSource)内置重连机制,网络恢复后会自动重新连接,无需额外代码。
通过这个案例,你可以清晰看到 SSE 的优势:实现简单(几行代码就能建立实时连接)、无需额外协议(基于 HTTP)、自带重连机制。如果你的场景只需要服务器单向推送数据(如实时通知、进度更新),SSE 会是比 WebSocket 更轻量的选择。
12、选 SSE 还是选 WebSocket?12.1 SSE 与 WebSocket 全面对比来对 Server-Sent Events (SSE) 和 WebSocket 进行一场全面、深入的对比。
18 为了更直观地理解两者的工作模式差异,请看下面的序列图:
19
1)协议与连接建立:
SSE 协议与连接建立:
a. 基于纯粹的 HTTP。客户端发起一个普通的 HTTP GET 请求,并携带特殊的头 Accept: text/event-stream。b. 服务器响应后,保持这个 TCP 连接打开,并开始发送数据流 ,直到遇到结束标记。c. 这是一种长连接的 HTTP 用法。WebSocket 协议与连接建立:
a. 基于独立的 WebSocket 协议。连接始于一个特殊的 HTTP 请求,即 “协议升级”请求(Connection: Upgrade, Upgrade: websocket)。b. 服务器响应 HTTP 101 Switching Protocols 后,最初的 HTTP 连接被替换为 WebSocket 连接,此后通信不再遵循 HTTP 协议,而是在其之上建立一个全双工的通道。2)数据流与通信模式:
SSE:单向通信。设计初衷就是让服务器能够主动、高效地向客户端推送数据。•数据是文本流,格式简单且可读性强。每条消息可以附带一个事件类型(event:)和一个 ID(id:)。•客户端使用 EventSource API 监听来自服务器的事件。
WebSocket:全双工通信。在连接建立后,客户端和服务器处于完全平等的地位,可以随时、任意地相互发送消息。 另外,WS 协议 支持文本和二进制数据,灵活性极高,非常适合需要频繁双向交互的场景(如游戏、协作编辑)。
3)能力与特性:
SSE:内置自动重连机制。如果连接断开,EventSource 对象会自动尝试重新连接,并在重连后自动发送上一个收到的事件 ID,服务器据此可判断错过了哪些消息,实现数据恢复。SSE 有出色的浏览器支持。所有现代浏览器(Chrome, Firefox, Safari, Edge)都原生支持,Internet Explorer (古代浏览器)完全不支持(通常需要 polyfill 或降级方案)。
WebSocket:无自动重连。连接断开后,需要开发者手动编写重连逻辑和状态恢复逻辑。WS 协议比 SSE 协议有更广泛的浏览器支持,包括 Internet Explorer 10+。
4)开发与集成:
SSE:开发与集成 非常简单。服务器端几乎不需要特殊的库,任何能输出 HTTP 流的后端语言都可以实现。客户端 API 也非常直观。与现有 HTTP 认证、CORS 机制完全兼容,处理方式与普通 HTTP 请求一致。
WebSocket:开发与集成 相对复杂。服务器端需要支持 WebSocket 协议的库(如 ws for Node.js, Socket.IO 等)。客户端需要处理连接状态、心跳包等。 虽然升级握手是 HTTP,但后续通信是独立协议,因此一些复杂的网络环境(如某些代理服务器)可能会带来问题。
12.2 SSE 与 WebSocket 到底该如何选择?选择的关键在于应用场景和核心需求。
选择 SSE 的场景:
选择 SSE 的场景包括:
1)服务器到客户端的单向数据流。2)简单和快速实现是关键因素。3)需要自动错误恢复(重连)。4)数据传输格式是文本(如 JSON),且不需要二进制。SSE 典型的应用场景包括:
1)实时新闻推送、体育比分更新。2)金融报价行情(如股票价格变动)。3)社交媒体动态更新(如 Twitter 时间线)。4)服务器日志流监控。5)AI 处理进度或结果的流式输出。
选择 WebSocket 场景:
选择 WebSocket 的场景包括:
1)真正的实时双向通信,客户端和服务器都需要频繁地发送消息。2)需要传输二进制数据(如视频、音频、图像碎片)。3)构建交互性极强的应用,其中低延迟至关重要。WebSocket 典型的应用场景包括:
1)实时在线聊天应用(如 Slack、Discord、RainbowChat-Web)。2)多人在线游戏。3)协同编辑工具(如 Google Docs)。4)实时仪表盘和控制面板(需要双向控制)。2012.3 WebSocket+SSE 混合架构一般来说大型应用场景,强网用 WebSocket、弱网适合使用 SSE ,这就是 WebSocket+SSE 混合架构。强网用 WebSocket、弱网自动降级到 SSE 的混合架构, 核心在于网络质量动态评估和双通道无缝切换。
WebSocket+SSE 混合架构 具体实现方案如下:
21
核心模块:网络质量探针(客户端) 实现
class NetworkProbe {
// 关键指标
static RTT_THRESHOLD = 300 // RTT 超过 300ms 视为弱网
static PACKET_LOSS_THRESHOLD = 0.2 // 丢包率>20%触发降级
// 网络状态检测
async check() {
}
// 实际测量方法
_measure() {
}
}
核心模块:双协议连接管理器(客户端)
class HybridConnection {
constructor() {
}
// 智能连接初始化
async connect() {
}
// WebSocket 初始化
_initWebSocket() {
}
// SSE 初始化
_initSSE() {
}
// 统一消息处理
_handleMessage = (event) => {
}
// 发送消息(自动选择协议)
send(data) {
}
// 协议切换(核心!)
async switchProtocol() {
}
}
12.4 AI 大模型中该选择 SSE 协议还是 WebSocket?直接答案:对于绝大多数 chat2ai 应用 优先选择 SSE (Server-Sent Events)。复杂的 chat2ai 应用 优先选择 WebSocket。
但这并非绝对,我们需要根据具体的功能需求来决定。下面我将为你进行详细的分析和推理。
12.4.1)核心决策分析:
AI 聊天应用的核心交互是:
1)客户端发送一条消息(一个问题)。2)服务器接收后,调用大语言模型(LLM)API。3)服务器将模型流式返回的答案(逐词或逐句)实时推送给客户端。4)客户端实时渲染这个流式的答案,营造出“打字机”效果。这个过程的关键在于第 3 步,即服务器向客户端的单向数据推送。这正是 SSE 的绝对主场。
12.4.2)为什么 SSE 是更优的选择?
以下流程图清晰地展示了基于不同技术方案的聊天交互过程,其中突出了 SSE 方案的巨大优势:
22
正如上图所示:SSE 方案在实现上更加直接和高效,因为它基于 HTTP,并且专门为服务器到客户端的单向数据流设计。
此外,SSE 还带来了以下巨大优势:
1)开发复杂度极低:
a. 后端:你不需要引入任何复杂的 WebSocket 库(如 ws, Socket.IO)。你只需要建立一个普通的 HTTP 路由(如 POST /chat 用于发送消息,GET /chat/stream 用于接收流),并在控制器中输出 text/event-stream 格式的响应流。b. 前端:使用浏览器原生的 EventSource API 即可轻松监听数据流,几行代码就能实现。无需实例化和管理 WebSocket 连接对象。2)出色的兼容性与可维护性:
a. SSE 基于 HTTP,这意味着它更容易通过公司防火墙、代理,与现有的认证系统(如 Cookie、JWT)、CORS 策略协同工作,几乎不会遇到奇怪的网络问题。b. 在浏览器“网络”选项卡中,SSE 的流清晰可见,易于调试。每个消息都是可读的文本,调试体验非常好。3)内置的自动重连与断点续传机制:
a. 这是 SSE 的“杀手级特性”。网络连接不稳定是移动端的常见问题。如果用户在接收一个很长答案的过程中网络中断,SSE 会在网络恢复后自动重新连接。b. 更强大的是,SSE 协议支持发送最后一个消息的 ID。服务器可以识别出这个 ID,并判断客户端错过了哪些数据,从而从断点处继续发送,而不是重新开始生成整个回答。这既节省了昂贵的 API 调用费用,也提升了用户体验。这在 WebSocket 中需要手动实现所有逻辑,非常复杂。12.4.3)WebSocket 的适用场景:
虽然 SSE 是主流选择,但在 chat2ai 应用变得非常复杂时,WebSocket 可能会成为更好的选择。
在以下情况下, 应该考虑使用 WebSocket:
1)需要极高频的双向通信:不仅仅是用户提问->AI 回答。例如:
a. 实时协作编辑:多个用户同时编辑一份由 AI 生成的文档,每个人的输入都需要实时同步给其他所有人。b. AI 多人游戏:基于 AI 生成剧情和环境的实时互动游戏,玩家的每一个动作都需要实时影响虚拟世界。2)当需要传输二进制数据的时候:有的聊天应用不仅支持文本,还支持实时语音对话(客户端录音发送二进制音频流,服务器返回 AI 语音二进制流)。WebSocket 对二进制数据的支持是天生的。
3)你需要非常精确的控制心跳和连接状态: - WebSocket 允许 手动发送 Ping/Pong 帧来检测连接活性,虽然复杂,但给了开发人员最大的控制权。
12.4.4)传输协议选型 结论与建议:1)起步和绝大多数情况:从 SSE 开始。这是最直接、最高效、最能给你带来稳定体验的选择。使用 sse 遇到的技术挑战会更少,开发速度更快。ChatGPT、Claude 等绝大多数顶级应用都使用 SSE 不是没有道理的。
2)未来如果需要扩展:采用混合架构。如果应用未来需要加入上述 WebSocket 的适用功能(如实时语音),完全可以同时使用两种协议:使用 SSE 专门处理 AI 文本答案的流式推送。使用 WebSocket 专门处理 实时语音、实时协作等真正的双向通信功能。或者 强弱结合,自动切换。
因此,对于 chat2ai 的传输协议选型答案是:优先选择 SSE。
13、参考资料
[0] EventSource API Docs
[1] Web 端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
[2] SSE 技术详解:一种全新的 HTML5 服务器推送事件技术
[3] 使用 WebSocket 和 SSE 技术实现 Web 端消息推送
[4] 详解 Web 端通信方式的演进:从 Ajax、JSONP 到 SSE、Websocket
[5] 使用 WebSocket 和 SSE 技术实现 Web 端消息推送
[6] 一文读懂前端技术演进:盘点 Web 前端 20 年的技术变迁史
[7] WebSocket 从入门到精通,半小时就够!
[8] 网页端 IM 通信技术快速入门:短轮询、长轮询、SSE、WebSocket
[9] 搞懂现代 Web 端即时通讯技术一文就够:WebSocket、socket.io、SSE
[10] 大模型时代多模型 AI 网关的架构设计与实现
[11] 全民 AI 时代,大模型客户端和服务端的实时通信到底用什么协议?
[12] 通俗易懂:AI 大模型基于 SSE 的实时流式响应技术原理和实践示例
[13] Web 端实时通信技术 SSE 在携程机票业务中的实践应用
[14] ChatGPT 如何实现聊天一样的实时交互?快速读懂 SSE 实时“推”技术
即时通讯技术学习:
移动端 IM 开发入门文章:《新手入门一篇就够:从零开发移动端 IM》
开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-4885-1-1.html)







评论