单向实时通信技术SSE

SSE概述

Server-Sent Events 服务器推送事件,简称 SSE,是一种基于HTTP协议的技术,允许服务端向客户端主动推送请求。

其核心特点是流式传输—— 服务端可将数据分块逐步发送,比如当前大语言模型的流式响应就是将先计算出来的数据返回给用户,然后边计算边返回。
在这里插入图片描述

与其他实时通信技术的对比

技术类型SSEWebSocket轮询(Polling)
连接性质单向(服务端→客户端)双向全双工客户端主动请求
HTTP 依赖基于 HTTP 长连接独立协议(需握手升级)|TCP多次短连接
实现复杂度简单(服务端逻辑轻量)复杂(需处理双向通信)极低
典型场景新闻推送、日志流、AI 流式响应聊天应用、实时游戏简单状态查询
浏览器兼容性主流浏览器支持(IE 除外)现代浏览器支持全兼容
数据类型文本或使用 Base64 编码和 gzip 压缩的二进制消息类型广泛类型广泛

服务端实现

SSE 协议非常简单,本质是浏览器发起 http 请求,服务器在收到请求后,返回状态与数据,并附带以下 headers:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
  • SSE API规定推送事件流的 MIME 类型为 text/event-stream
  • 必须指定浏览器不缓存服务端发送的数据,以确保浏览器可以实时显示服务端发送的数据。
  • SSE 是一个一直保持开启的 TCP 连接,所以 Connection 为 keep-alive。

Spring Boot开发中不需要设置请求头

使用标注@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)时,Spring会自动设置正确的Content-Type。

消息格式

每条消息由一行或多行字段(eventidretrydata)组成

data 字段 (必需):可以有多行data字段,最终会合并为一个值(用\n连接)

event 字段 (可选):指定事件类型

id 字段 (可选):设置消息ID,用于断线重连时恢复,客户端会在重连时通过Last-Event-ID头发送最后收到的ID

retry 字段 (可选):指定重连时间(毫秒)

浏览器 API

在浏览器端,可以使用 JavaScript 的 EventSource API 创建 EventSource 对象监听服务器发送的事件。一旦建立连接,服务器就可以使用 HTTP 响应的 ‘text/event-stream’ 内容类型发送事件消息,浏览器则可以通过监听 EventSource 对象的 onmessageonopenonerror 事件来处理这些消息。

建立连接

// 第二个是可选值,包含 withCredentials 属性,表示是否发送凭证(cookie、HTTP认证信息等)到服务端,默认为 false。
const eventSource = new EventSource('http_api_url', { withCredentials: true })

监听事件

EventSource 对象本身继承自 EventTarget 接口,因此可以使用 addEventListener() 方法来监听事件。EventSource 对象触发的事件主要包括以下三种:

  • open 事件:当成功连接到服务端时触发。
  • message 事件:当接收到服务器发送的消息时触发。该事件对象的 data 属性包含了服务器发送的消息内容。
  • error 事件:当发生错误时触发。该事件对象的 event 属性包含了错误信息。
/ 初始化 eventSource 等省略eventSource.onopen = function(event) {console.log('Connection opened')
}eventSource.onmessage = function(event) {console.log('Received message: ' + event.data);
}eventSource.onerror = function(event) {console.log('Error occurred: ' + event.event);
})

实践

服务端

使用 Node.js 实现 SSE 的简单示例:

const http = require('http')
const fs = require('fs')http.createServer((req, res) => {const url = req.urlif (url === '/' || url === 'index.html') {// 如果请求根路径,返回 index.html 文件fs.readFile('index.html', (err, data) => {if (err) {res.writeHead(500)res.end('Error loading')} else {res.writeHead(200, {'Content-Type': 'text/html'})res.end(data)}})} else if (url.includes('/sse')) {// 如果请求 /events 路径,建立 SSE 连接res.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive','Access-Control-Allow-Origin': '*', // 允许跨域})// 每隔 1 秒发送一条消息let id = 0const intervalId = setInterval(() => {res.write(`event: customEvent\n`)res.write(`id: ${id}\n`)res.write(`retry: 30000\n`)const params = url.split('?')[1]const data = { id, time: new Date().toISOString(), params }res.write(`data: ${JSON.stringify(data)}\n\n`)id++if (id >= 10) {clearInterval(intervalId)res.end()}}, 1000)// 当客户端关闭连接时停止发送消息req.on('close', () => {clearInterval(intervalId)id = 0res.end()})} else {// 如果请求的路径无效,返回 404 状态码res.writeHead(404)res.end()}
}).listen(3000)console.log('Server listening on port 3000')

使用 Spring Boot实现 SSE 的简单示例:

@RestController
@RequestMapping("/sse")
public class SSEmitterController {@GetMapping("/stream")public SseEmitter stream() {// 发送Map集合Map<String, Object> dataMap = new HashMap<>();dataMap.put("userId", "user123");dataMap.put("content", "订单已发货");dataMap.put("timestamp", new Date());// 用于创建一个 SSE 连接对象SseEmitter emitter = new SseEmitter();// 在后台线程中模拟实时数据try {// emitter.send() 方法向客户端发送消息// 使用SseEmitter.event()创建一个事件对象,设置事件名称和数据emitter.send(SseEmitter.event().name("message").data(dataMap ));} catch (IOException e) {// 发生错误时,关闭连接并报错emitter.completeWithError(e);}return emitter;}
}

浏览器

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>SSE Demo</title>
</head>
<body><h1>SSE Demo</h1><button onclick="connectSSE()">建立 SSE 连接</button>  <button onclick="closeSSE()">断开 SSE 连接</button><br /><br /><div id="message"></div><script>const messageElement = document.getElementById('message')let eventSource// 建立 SSE 连接const connectSSE = () => {eventSource = new EventSource('http://127.0.0.1:3000/sse?content=xxx')// 监听消息事件eventSource.addEventListener('customEvent', (event) => {const data = JSON.parse(event.data)messageElement.innerHTML += `${data.id} --- ${data.time} --- params参数:${JSON.stringify(data.params)}` + '<br />'})eventSource.onopen = () => {messageElement.innerHTML += `SSE 连接成功,状态${eventSource.readyState}<br />`}eventSource.onerror = () => {messageElement.innerHTML += `SSE 连接错误,状态${eventSource.readyState}<br />`}}// 断开 SSE 连接const closeSSE = () => {eventSource.close()messageElement.innerHTML += `SSE 连接关闭,状态${eventSource.readyState}<br />`}</script>
</body>
</html>

Fetch 实现

浏览器 EventSource API 限制,在使用 SSE 时不能自定义请求头、只能发出 GET 请求,且在大多数浏览器中,URL 限制 2000个字符

服务端

const http = require('http')
const fs = require('fs')http.createServer((req, res) => {const url = req.urlif (url === '/' || url === 'index-fetch.html') {// 如果请求根路径,返回 ndex-fetch.html 文件fs.readFile('index-fetch.html', (err, data) => {if (err) {res.writeHead(500)res.end('Error loading')} else {res.writeHead(200, {'Content-Type': 'text/html'})res.end(data)}})} else if (url.includes('/fetch-sse')) {// 如果请求 /events-fetch 路径,建立连接res.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive','Access-Control-Allow-Origin': '*', // 允许跨域})let body = ''req.on('data', chunk => {body += chunk})// 每隔 1 秒发送一条消息let id = 0const intervalId = setInterval(() => {const data = { id, time: new Date().toISOString(), body: JSON.parse(body) }res.write(JSON.stringify(data))id++if (id >= 10) {clearInterval(intervalId)res.end()}}, 1000)// 当客户端关闭连接时停止发送消息req.on('close', () => {clearInterval(intervalId)id = 0res.end()})} else {// 如果请求的路径无效,返回 404 状态码res.writeHead(404)res.end()}
}).listen(3001)console.log('Server listening on port 3001')

浏览器

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>fetchSSE Demo</title>
</head>
<body><h1>fetchSSE Demo</h1><button onclick="connectFetch()">建立 fetchSSE 连接</button><button onclick="closeSSE()">断开 fetchSSE 连接</button><br /><br /><div id="message"></div><script>const messageElement = document.getElementById('message')let controller// 建立 FETCH-SSE 连接const connectFetch = () => {controller = new AbortController()fetchEventSource('http://127.0.0.1:3001/fetch-sse', {method: 'POST',body: JSON.stringify({content: 'xxx'}),signal: controller.signal,onopen: () => {messageElement.innerHTML += `FETCH 连接成功<br />`},onclose: () => {messageElement.innerHTML += `FETCH 连接关闭<br />`},onmessage: (event) => {const data = JSON.parse(event)messageElement.innerHTML += `${data.id} --- ${data.time} --- body参数:${JSON.stringify(data.body)}` + '<br />'},onerror: (e) => {console.log(e)}})}// 断开 FETCH-SSE 连接const closeSSE = () => {if (controller) {controller.abort()controller = undefinedmessageElement.innerHTML += `FETCH 连接关闭<br />`}}const fetchEventSource = (url, options) => {fetch(url, options).then(response => {if (response.status === 200) {options.onopen && options.onopen()return response.body}}).then(rb => {const reader = rb.getReader()const push = () => {// done 为数据流是否接收完成,boolean// value 为返回数据,Uint8Arrayreturn reader.read().then(({done, value}) => {if (done) {options.onclose && options.onclose()return}options.onmessage && options.onmessage(new TextDecoder().decode(value))// 持续读取流信息return push()})}// 开始读取流信息return push()}).catch((e) => {options.error && options.error(e)})}
</script></html>

参考博客:https://juejin.cn/post/7221125237500330039

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/89137.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI Agent的记忆体系与架构设计

LLM本质上是无状态的模型&#xff0c;每次调用都像一次“短暂失忆”。为了让 AI Agent真正理解上下文、具备个性化交互和任务持续性&#xff0c;引入记忆系统至关重要。本文将从技术与架构角度出发&#xff0c;系统介绍构建短期和长期记忆的最佳实践。 一、AI Agent中的记忆类型…

FastJson的反序列化问题入门

FastJson 简介 他是一个java的依赖库主要是用来进行处理web的json数据&#xff0c;比如就类似于序列化和反序列化 演示 先创建一个类&#xff0c;这个fastjson触发的条件主要就是要处理的类中有 set&#xff0c;get方法 这个方法主要是依赖了 封装思想 导入get , set 方法 …

Lavazza拉瓦萨再度牵手兰博基尼汽车 百年咖啡注入超跑速度

2025年6月12日&#xff0c;继去年首次合作反响热烈之后&#xff0c;有着130年历史的全球咖啡巨头Lavazza拉瓦萨与兰博基尼汽车再度携手开启跨界合作。这不仅是两个传奇品牌的基因共振&#xff0c;更是一场关于咖啡豆与机械美学的深度创新实验。 Lavazza&#xff0c;这个名字在意…

Arduino入门教程:​​​​​​​2、代码基础

飞书文档https://x509p6c8to.feishu.cn/docx/Qyv3dvEIDozdcvxlbkRc2lDdnMc 一、基本程序结构 #include <Arduino.h> void setup() {}void loop() {} //头文件->可以理解为Arduino工具箱 #include <Arduino.h> //初始化函数&#xff0c;只执行一次&#xff0c;…

安卓9.0系统修改定制化____系列 ROM解打包 修改 讲解 导读篇

专栏系列前言&#xff1a; &#x1f49d;&#x1f49d;&#x1f49d;本专栏作者从事rom系统修改以及手机维修 刷机多年。从当年山寨机开始。历经安卓4.--至目前的安卓15.合作伙伴遍及各类工作室以及PDA商家 私人玩友等。在广告机 平板 pda设备 会议机 车机的rom修改中略有经…

免单统计 - 华为OD机试真题(JavaScript题解)

华为OD机试题库《C》限时优惠 9.9 华为OD机试题库《Python》限时优惠 9.9 华为OD机试题库《JavaScript》限时优惠 9.9 针对刷题难&#xff0c;效率慢&#xff0c;我们提供一对一算法辅导&#xff0c; 针对个人情况定制化的提高计划&#xff08;全称1V1效率更高&#xff09;。 看…

pikachu靶场通关笔记25 SQL注入08-布尔盲注(base on boolian 手工注入+脚本注入 两种方法渗透)

目录 一、SQL注入 二、布尔盲注 三、源码分析 四、渗透实战 1、SQL注入探测 &#xff08;1&#xff09;输入已有账户 &#xff08;2&#xff09;输入不存在账户 &#xff08;3&#xff09;输入单引号等可能报错的情况 2、手工注入 &#xff08;1&#xff09;探测数据…

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…

数字IC后端实现之Innovus中各种cell名字前缀物理含义

社区新一期T28 a7core 和T12nm A55数字IC后端实现训练营直播课开始预约报名啦&#xff01; 今天给大家分享下Innovus中各种常见cell命名规则及其物理含义。知道这些信息后&#xff0c;后续我们在debug后端项目问题时就可以更高效地定位到具体问题。做为数字IC后端工程师&#…

腐烂之息-(Breath of Decay VR ) 硬核VR游戏

《腐烂之息》 是一款沉浸式VR生存射击游戏&#xff0c;带你进入一个充满丧尸身影的末日世界。在灾难爆发三年后&#xff0c;你将从培养仓中醒来&#xff0c;面对一个废墟般的世界。作为幸存者&#xff0c;你必须依靠自己的智慧&#xff0c;在这个充满危险的世界中生存、同时揭开…

ChatGPT 辅助 PyTorch 开发:从数据预处理到 CNN 图像识别的全流程优化

技术点目录 第一章、ChatGPT与DeepSeek等大语言模型助力AI编程必备技能详解第二章、Python基础知识串讲第三章、PyTorch简介与环境搭建第五章、ChatGPT和DeepSeek等大语言模型助力统计分析与可视化第六章、ChatGPT和DeepSeek等大语言模型助力前向型神经网络第七章、ChatGPT和De…

js正则表达式使用 test match

文章目录 一、介绍二、案例regex.test(ip)用法ip.match(regex)用法 三、regex.test(ip) 和 ip.match(regex) 区别 一、介绍 正则表达式&#xff08;Regular Expression&#xff0c;简称 regex 或 regexp&#xff09;是一种用于描述字符串模式的工具。它可以用来搜索、匹配、替…

强化学习用于长期异质性效应评估学习笔记(三)

在【实验科学中策略的长期异质性效应量化方案探索&#xff08;一&#xff09;】提到了强化学习估计长期价值&#xff0c;将 A/B 策略看作是策略 π 的不同版本&#xff0c;构造马尔可夫决策过程&#xff08;MDP&#xff09;或部分可观测 MDP&#xff08;POMDP&#xff09;&…

for...in 循环深度解析

在JavaScript开发中&#xff0c;for...in循环是一个常见的语法结构&#xff0c;但它在遍历数组时存在很多潜在问题。这些问题如果不加以注意&#xff0c;可能导致意想不到的bug和性能问题。 for…in 循环的本质 for...in循环是设计用来遍历对象属性的&#xff0c;而不是专门为…

MH2213 32位Arm® Cortex®-M3 Core核心并内嵌闪存和SRAM

MH2213 32位Arm Cortex-M3 Core核心并内嵌闪存和SRAM 概述&#xff1a; MH2213 3 2位的Arm Cortex-M3 Core为实现MCU的需要提供了低成本的平台、缩减的引脚数目、降低的系统功耗&#xff0c;同时提供卓越的计算性能和先进的中断系统响应。 基础功能配表&#xff1a; MH2213 32位…

阿里云ACP云计算备考笔记 (6)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …

SQL注入攻击原理与防御全解析

目录 一、引言 二、SQL 注入原理 2.1 SQL 注入的概念 2.2 SQL 注入产生的原因 2.3 SQL 注入的本质 2.4 SQL 注入的关键点 三、SQL 注入的实现方法 3.1 常见的 SQL 注入场景 3.2 不同类型的 SQL 注入方式 3.3 SQL 注入的一般流程 四、SQL 注入的危害 4.1 数据泄露 …

【游戏设计】游戏视角类型及核心特点分析

目录 1 俯视视角 (Top-Down View)1.1 核心特点1.2 典型应用场景1.3 优缺点 2 侧视视角 (Side View)2.1 核心特点2.2 典型应用场景2.3 优缺点 3 等轴测视角 (Isometric View)3.1 核心特点3.2 典型应用场景3.3 优缺点 4 三种视图类型比较5 视角类型选择的黄金法则 视角&#xff0…

本地部署多智能体Manus

Manus作为一款通用型AI Agent产品,凭借其出色的表现引发了广泛的关注和热议。而如今,我们可以通过本地部署多智能体Manus,打造属于自己的智能协作平台,以满足特定需求并实现更高的自主性和安全性。 部署意义 数据安全与隐私保护 :本地部署使得数据无需上传至云端,可…

​​​​​​​《TCP/IP协议卷1》第9章 IP选路

&#x1f30d; 思考&#xff1a;IP 选路是什么&#xff1f;路由表的作用是什么&#xff1f;路由表是如何初始化的&#xff1f;如何更新的 &#xff1f;IP 如何根据路由表进行选路的&#xff1f;选路的方法有哪些&#xff1f; IP 选路是什么&#xff1f; IP选路&#xff0c;也…