AI大模型的文本流如何持续吐到前端,服务端实时通信技术 SSE(Server-Sent Events) 认知

99%的焦虑都来自于虚度时间和没有好好做事,所以唯一的解决办法就是行动起来,认真做完事情,战胜焦虑,战胜那些心里空荡荡的时刻,而不是选择逃避。不要站在原地想象困难,行动永远是改变现状的最佳方式

写在前面


  • 没接触过 SSE(Server-Sent Events),AI大模型出来之后,一直以为文本流是用 WebSocket 做的
  • 偶然看到返回到报文格式是 text/event-stream,所以简单认知,整理笔记
  • 博文内容涉及 SSE 认知,以及对应的 Demo
  • 理解不足小伙伴帮忙指正 :),生活加油

99%的焦虑都来自于虚度时间和没有好好做事,所以唯一的解决办法就是行动起来,认真做完事情,战胜焦虑,战胜那些心里空荡荡的时刻,而不是选择逃避。不要站在原地想象困难,行动永远是改变现状的最佳方式

持续分享技术干货,感兴趣小伙伴可以关注下 ^_^


好奇大模型回答的文本流推送的是一句句话,还是一个个分词,然后看了下,才发现用的并不是 WebSocket ,是 SSE

下面是部分请求头,我们可以看到,请求报文类型为 content-type:text/event-stream; charset=utf-8

1
2
3
4
5
6
7
access-control-allow-credentials:true
content-type:text/event-stream; charset=utf-8
date:Sat, 15 Feb 2025 02:58:53 GMT
server:elb
strict-transport-security:max-age=31536000; includeSubDomains; preload
x-content-type-options:nosniff
x-ds-trace-id:20bd419739717b9d60a3224ab65b3620

text/event-stream; charset=utf-8 是一种 Server-Sent Events(SSE)MIME 类型。SSE 是一种允许服务器向客户端推送实时更新的技术,它基于 HTTP 协议,适用于需要服务端单向、实时数据传输的场景,如股票行情、新闻推送、社交媒体更新等。

通过下面的截图也可以看到,实际返回的是分词数据

deepseek 的

在这里插入图片描述

腾讯元宝 的

在这里插入图片描述

SSE 认知

SSEHTML5 规范的一部分,具体的规范文档可以在 W3C(万维网联盟)的官方网站上看到

查看规范以及对应的 API 文档,可以查看 W3C 官网对应的内容

https://htmlspecs.com/#server-sent-events

实际的代码使用 Demo 以及浏览器兼容问题,可以 Mozilla 官网看到

https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events

用一句话讲 ,SSE 即使服务器能够通过 HTTP 或使用专用的服务器推送协议向网页推送数据,引入了EventSource 接口,该API 包括创建一个EventSource 对象并注册一个事件监听器。

1
2
3
4
var source = new EventSource('updates.cgi');
source.onmessage = function (event) {
alert(event.data);
};

在服务器端,通过 MIME 类型为text/event-stream 报文类型,下面是一个 Demo,实际可能需要考虑更多,比如异常处理等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.example.ssestock;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@RestController
public class StockController {

private final ExecutorService executor = Executors.newCachedThreadPool();
private final Random random = new Random();

@GetMapping("/stock-price")
public SseEmitter streamStockPrice() {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); // 设置超时时间为最大值
executor.execute(() -> {
try {
// 发送初始连接事件
emitter.send(SseEmitter.event().name("connectionEstablished").data("连接已建立"));

while (true) {
double price = 100 + random.nextDouble() * 10; // 生成100到110之间的随机价格
String message = String.format("data: %.2f\n\n", price);
emitter.send(SseEmitter.event().name("priceUpdate").data(price));

// 模拟偶尔的错误事件
if (random.nextDouble() < 0.05) { // 5% 的概率发送错误事件
emitter.send(SseEmitter.event().name("error").data("价格数据获取失败"));
}

Thread.sleep(1000); // 每秒发送一次
}
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
}
});

return emitter;
}
}

可以使用不同的事件类型来区分事件。上面是一个包含 "priceUpdate" 和 "error" 两种事件类型的流,默认的事件类型是 "message"。事件流始终以 UTF-8 解码。无法指定其他字符编码。

在客户端,监听对应的事件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const sse = new EventSource("/api/v1/stock-price");

/*
* 这将仅监听类似下面的事件
*
* event: priceUpdate
* data: useful data
* id: someid
*/
sse.addEventListener("priceUpdate", (e) => {
console.log(e.data);
});

/*
* 同理,以下代码将监听具有字段 `event: error` 的事件
*/
sse.addEventListener("error", (e) => {
console.log(e.data);
});

/*
* “message”事件是一个特例,因为它可以捕获没有 event 字段的事件,
* 以及具有特定类型 `event:message` 的事件。
* 它不会触发任何其他类型的事件。
*/
sse.addEventListener("message", (e) => {
console.log(e.data);
});

事件流请求可以像普通 HTTP 请求一样使用 HTTP 301 和 307 重定向进行重定向。如果连接关闭,客户端将重新连接;可以通过使用 HTTP 204 无内容响应代码来告知客户端停止重新连接。

需要注意的是:

当不使用 HTTP/2 时,服务器发送事件(SSE)受到打开连接数的限制,这个限制是对于浏览器的,并且设置为非常低的数字(6),打开多个选项卡时可能会特别痛苦。在 Chrome 和 Firefox 中,这个问题已被标记为“不会修复”。这个限制是每个浏览器和域名的,这意味着你可以在所有标签页中打开 6 个 SSE 连接到 www.example1.com,以及另外 6 个 SSE 连接到 www.example2.com(来源:Stackoverflow)。`当使用 HTTP/2 时,最大并发 HTTP 流的数量是由服务器和客户端协商的(默认为 100)。
`

SSE 可以做什么

先看看 缺点:

SSE(Server-Sent Events)的缺点主要包括:

  1. 单向通信:SSE仅支持服务器向客户端的单向通信,客户端无法主动向服务器发送数据
  2. 浏览器并发限制:HTTP/1 浏览器对单个域名的EventSource连接数有限制(通常为6个)
  3. 仅支持文本数据:SSE只能传输UTF-8文本,不支持二进制数据(如图片、音频、视频流),限制了其在多媒体应用中的使用。

优点:

  • 基于 HTTP 协议:直接复用现有 HTTP 基础设施,无需额外协议(如 WebSocket 的 ws:// 或 wss://),也无需处理复杂的握手和协议升级。
  • 浏览器原生支持:通过 EventSource API 直接使用,无需引入第三方库
  • 低带宽消耗:相比 WebSocket 的帧头开销,SSE 的协议头更简单,适合高频小数据量推送(如实时日志、状态更新)。
  • 内置重连机制:连接中断时,浏览器会自动尝试重新连接,开发者无需手动处理。
  • 支持历史事件 ID:可通过 last-event-id 请求头实现断点续传,避免数据丢失。
  • 连接复用:一个 HTTP 连接支持多次数据推送,减少频繁建立连接的开销(对比短轮询)。
  • 无双向通信风险:由于 SSE 是单向的(服务器→客户端),减少了客户端主动攻击的入口面。

所以选择 SSE 的场景:

当需要服务器向客户端持续推送数据,且无需客户端频繁回传时,SSE 是比 WebSocket 更简单高效的解决方案。同时相比 http 轮询的方式,节省了资源,实现长链接复用,对于分布式的场景,可以考虑使用 MQ 或则 Redis 实现分布式广播。

常见应用:

  • 实时通知:如邮件提醒、社交媒体动态更新。
  • 数据流监控:服务器日志流、IoT 设备状态推送,大模型的问答分词数据流。
  • 实时数据展示:股票行情、新闻推送、赛事比分。

当然 SSE 主要面向 B/S 架构,是浏览器实现服务器推送的轻量级方案。非浏览器场景下 SSE 可用但非最优,对 C/S 架构或服务间通信,需权衡实现成本与需求(如单向性、HTTP 兼容性)。若需双向通信或高性能数据传输,建议选择 WebSocket、gRPC 或 MQTT

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)


https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events

https://htmlspecs.com/#server-sent-events


© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

AI大模型的文本流如何持续吐到前端,服务端实时通信技术 SSE(Server-Sent Events) 认知

https://liruilongs.github.io/2025/01/26/待发布/AI大模型的文本流如何持续吐到前端,BS架构实时通信的技术 SSE(Server-Sent Events) 认知/

发布于

2025-01-26

更新于

2025-02-17

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×