当前位置: 首页 > article >正文

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

写在前面


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

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

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


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

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

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 对象并注册一个事件监听器。

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

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

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 解码。无法指定其他字符编码。

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

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) 认知

写在前面 没接触过 SSE&#xff08;Server-Sent Events&#xff09;&#xff0c;AI大模型出来之后&#xff0c;一直以为文本流是用 WebSocket 做的偶然看到返回到报文格式是 text/event-stream,所以简单认知&#xff0c;整理笔记博文内容涉及 SSE 认知&#xff0c;以及对应的 D…...

Electron:使用electron-react-boilerplate创建一个react + electron的项目

使用 electron-react-boilerplate git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name cd your-project-name npm install npm start 安装不成功 在根目录加上 .npmrc文件 内容为 electron_…...

Spring Boot三:Springboot自动装配原理

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 原理初探 pom.xml 核心依赖在父工程中 spring-boot-dependencies所有的jar包都在这里管理 我们在写或者引入一些依赖的时候&#xff0c;不需要指定版本 启动器 <…...

【ISO 14229-1:2023 UDS诊断全量测试用例清单系列:第十八节】

ISO 14229-1:2023 UDS诊断服务测试用例全解析&#xff08;ResponseOnEvent_0x86服务&#xff09; 作者&#xff1a;车端域控测试工程师 更新日期&#xff1a;2025年02月14日 关键词&#xff1a;UDS协议、0x86服务、事件响应、ISO 14229-1:2023、ECU测试 一、服务功能概述 0x86…...

Qt 中使用 SQLite 数据库的完整指南

SQLite 是一款轻量级、嵌入式的关系型数据库&#xff0c;无需独立的服务器进程&#xff0c;数据以文件形式存储&#xff0c;非常适合桌面和移动端应用的本地数据管理。Qt 通过 Qt SQL 模块提供了对 SQLite 的原生支持&#xff0c;开发者可以轻松实现数据库的增删改查、事务处理…...

2024 年 CSDN 博客之星年度评选:技术创作与影响力的碰撞(统计时间2025-02-17 11:06:06)

摘要&#xff1a;在技术的海洋里&#xff0c;每一位博主都像是一座独特的灯塔&#xff0c;用自己创作的光芒照亮他人前行的道路。2024 年 CSDN 博客之星年度评选活动&#xff0c;正是对这些灯塔的一次盛大检阅&#xff0c;让我们看到了众多优秀博主在技术创作领域的卓越表现以及…...

Java零基础入门笔记:(3)程序控制

前言 本笔记是学习狂神的java教程&#xff0c;建议配合视频&#xff0c;学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili Scanner对象 之前我们学的基本语法中我们并没有实现程序和人的交互&#xff0c;但是Java给我们提供了这样一个工具类&…...

后端生成二维码,前端请求接口生成二维码并展示,且多个参数后边的参数没有正常传输问题处理

一、后端代码 1、controller GetMapping("/generateQRCode/{url}")ApiOperation(value "生成url链接二维码",notes "生成url链接二维码")public JsonResult<NewsQRCodeVo> generateQRCode(PathVariable String url,HttpServletRespons…...

(8/100)每日小游戏平台系列

项目地址位于&#xff1a;小游戏导航 新增一个打地鼠游戏&#xff01; 打地鼠&#xff08;Whack-a-Mole&#xff09;是一款经典的休闲游戏&#xff0c;玩家需要点击随机出现的地鼠&#xff0c;以获取分数。游戏时间有限&#xff0c;玩家需要在规定时间内尽可能多地击中地鼠&am…...

【jar包启动命令简单分享】

最近在做springcloud项目&#xff0c;整理了下启停脚本 批量启动脚本 #!/bin/bashAPP_HOME/data/java/ APP_NAMES("ruoyi-auth.jar""ruoyi-gateway.jar""ruoyi-modules-file.jar""ruoyi-modules-gen.jar""ruoyi-modules-job.jar…...

[Python人工智能] 五十.PyTorch入门 (5)快速搭建神经网络及模型保存

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建分类神经网络。这篇文章将介绍如何利用PyTorch快速构建神经网络,之前的代码比较复杂,通过自定义Net类实现,本文通过Torch函数定义神经网络。前面我们的Python人工智能主要以Tens…...

SpringBoot+Vue+数据可视化的动漫妆造服务平台(程序+论文+讲解+安装+调试+售后等)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在当今数字化高速发展的时代&#xff0c;动漫产业迎来了前所未有的繁荣&#xff0c;动漫…...

Go入门之语言变量 常量介绍

func main(){var a int8 10var b int 5var c int 6fmt.Println("a", a, "b", b, "c", c)d : 10fmt.Printf("a%v leixing%T\n", d, d) } main函数是入口函数,fmt包有三个打印的函数Println&#xff0c;Print&#xff0c;Printf。第…...

基于web的留守儿童网站的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

鸡兔同笼问题

鸡兔同笼问题是这样一个问题&#xff1a; 现有鸡、兔合装在一个笼子里。数头一共100个头&#xff0c;数脚一共300只脚。问有多少只鸡多少只兔&#xff1f; 在这里讨论这个问题的解法当然太小儿科了。但是y_tab这个C语言解释器只提供了1维数组。如果需要用到2维数组时&#xff…...

使用 Spring Boot 和 Canal 实现 MySQL 数据库同步

文章目录 前言一、背景二、Canal 简介三、主库数据库配置1.主库配置2.创建 Canal 用户并授予权限 四.配置 Canal Server1.Canal Server 配置文件2.启动 Canal Server 五.开发 Spring Boot 客户端1. 引入依赖2. 配置 Canal 客户端3. 实现数据同步逻辑 六.启动并测试七.注意事项八…...

中上211硕对嵌入式AI感兴趣,如何有效规划学习路径?

今天给大家分享的是一位粉丝的提问&#xff0c;中上211硕对嵌入式AI感兴趣&#xff0c;如何有效规划学习路径&#xff1f; 接下来把粉丝的具体提问和我的回复分享给大家&#xff0c;希望也能给一些类似情况的小伙伴一些启发和帮助。 同学提问&#xff1a; 中上211&#xff0c;…...

OpenCV中的边缘检测

边缘检测是图像处理和计算机视觉中的关键技术之一&#xff0c;旨在识别图像中像素强度发生显著变化的区域&#xff0c;这些区域通常对应于物体的边界或轮廓。边缘检测在机器视觉中具有重要的需求背景&#xff0c;主要体现在以下几个方面&#xff1a; 图像分割&#xff1a;边缘…...

Python爬虫-猫眼电影的影院数据

前言 本文是该专栏的第46篇,后面会持续分享python爬虫干货知识,记得关注。 本文笔者以猫眼电影为例子,获取猫眼的影院相关数据。 废话不多说,具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。接下来,跟着笔者直接往下看正文详细内容。(附带完整代码) …...

家里WiFi信号穿墙后信号太差怎么处理?

一、首先在调制解调器&#xff08;俗称&#xff1a;猫&#xff09;测试网速&#xff0c;网速达不到联系运营商&#xff1b; 二、网线影响不大&#xff0c;5类网线跑500M完全没问题&#xff1b; 三、可以在卧室增加辅助路由器&#xff08;例如小米AX系列&#xff09;90~200元区…...

【前端学习笔记】Webpack

1.介绍 Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具&#xff0c;它将 JavaScript、CSS、图片、字体等资源文件打包成一个或多个静态文件&#xff0c;以供浏览器使用。当 webpack 处理应用程序时&#xff0c;它会在内部从一个或多个入口点构建一个 依赖图(depend…...

数据结构(陈越,何钦铭)第三讲 树(上)

3.1 树与数的表示 3.1.1 顺序查找 int SequentialSearch(List Tbl,ElementType K){int i;Tbl->Element[0]K;for(iTbl->Length;Tbl->Element[i]!K;i--);return i; } typedef struct LNode *List; struct LNode{ElementType Element[MAXSIZE];int Length; };3.1.2 二分…...

【深度解析】图解Deepseek-V3模型架构-混合专家模型(MoE)

一、引言 最近非常火爆的DeepSeek-V3模型&#xff0c;是一个包含6710亿总参数的强大混合专家模型&#xff08;MoE&#xff09;&#xff0c;其中每个token激活370亿参数。该模型在DeepSeek-V2验证有效的核心架构基础上&#xff0c;采用多头潜在注意力&#xff08;MLA&#xff0…...

c#判断exe文件是不是7z或者rar的自解压文件

亲测可以实现检测7z的自解压&#xff0c;但是对于rar的自解压格式&#xff0c;最新版不支持&#xff0c;尝试修改回发现几乎检测成了exe文件&#xff0c;这显然是不正确的&#xff0c;其他版本未测试。 如下图所示&#xff0c;可以检测出自解压格式的7z文件&#xff0c;黑色显…...

富士SC2022,C325,C328打印机扫描到网络详细教程

前言&#xff1a; 在开始教程之前,我先声明目前该教程适用于FujiXerox apeos C325Z和FujiXerox DocuCentre SC2022打印机。这次教程以FujiXerox DocuCentre SC2022为例&#xff0c;该打印机IP地址为10.40.11.240。 前提条件 &#xff1a; 1. 安装打印机所需打印机和扫…...

涌现之谜:神经网络中的意识幻象与信息熵变

导言&#xff1a;黑箱中的幽灵剧场 当AlphaGo在棋盘第37手落下超越人类棋谱的"神之一着"时&#xff0c;观者感受到的震颤不亚于目睹意识的曙光。这种认知幻觉暴露了智能研究的基本困境&#xff1a;在权重矩阵的混沌涨落中&#xff0c;究竟诞生的是真正的认知主体&am…...

WEB安全--SQL注入--常见的注入手段

一、联表查询&#xff1a; 1.1原理&#xff1a; 当payload参数被后端查询语句接收到时&#xff0c;其中的非法语句通过union关联显示出其他的数据 1.2示例&#xff1a; #payload: -1 and union select 1,2,database()--#query: $sqlselect * from users where id-1 and union …...

wordpress get_footer();与wp_footer();的区别的关系

在WordPress中&#xff0c;get_footer() 和 wp_footer() 是两个不同的函数&#xff0c;它们在主题开发中扮演着不同的角色&#xff0c;但都与页面的“页脚”部分有关。以下是它们的区别和关系&#xff1a; 1. get_footer() get_footer() 是一个用于加载页脚模板的函数。它的主…...

人工智能3d点云之Pointnet++项目实战源码解读(点云分类与分割)

一.项目文件概述 二.数据读取模块配置 实际代码运行时是先定义与加载好模型&#xff0c;然后再去读取数据进来传入到模型网络中去训练。但现在反过来先读取数据开始。 进入ModelNetDataLoader类的_getitem方法, 做标准化的目的是处理异常大的数值 上面返回的cls是类别,相当于…...

IP 路由基础 | 路由条目生成 / 路由表内信息获取

注&#xff1a;本文为 “IP 路由” 相关文章合辑。 未整理去重。 IP 路由基础 秦同学学学已于 2022-04-09 18:44:20 修改 一. IP 路由产生背景 我们都知道 IP 地址可以标识网络中的一个节点&#xff0c;并且每个 IP 地址都有自己的网段&#xff0c;各个网段并不相同&#xf…...