深入理解 SSE:服务器发送事件及其在前后端中的实践
前言
放假前接到一个需求,关于前端从服务器获取实时数据,之前用的是定时器,由于原因,诸多原因,产品拉会讨论新的方案,最后改成用SSE技术实现同样效果。
在此之前我对SSE等知识了解得比较少,因此在实践过程中写成一篇博客,和大家一起交流学习。
在实时 Web 应用开发中,我们经常需要从服务器获取实时数据。传统的轮询方式效率低下,而 WebSocket 虽然功能强大但实现复杂。
介绍一种轻量级的实时服务器推送技术 ——SSE(Server-Sent Events),以及如何在 Express 后端和 Vue2 前端中实现它。
一、什么是 SSE?
SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 协议的技术,允许服务器主动向客户端持续推送数据。它的工作方式很简单:
- 客户端发起一次 HTTP 请求建立连接
- 服务器保持这个连接不关闭,有新数据时主动推送给客户端
- 数据以简单的文本格式传输,每次推送都遵循特定格式
- 当连接意外断开时,客户端会自动尝试重连
SSE 就像服务器给客户端开了一个专属推送通道,服务器可以随时向这个通道发送消息,而不需要客户端反复请求。
二、SSE 与 WebSocket 的区别
SSE 和 WebSocke都能实现服务器向客户端推送数据,但适用场景不同:
特性 | SSE | WebSocket |
---|---|---|
通信方式 | 单向(服务器→客户端) | 双向(全双工) |
协议 | 基于 HTTP | 独立的 WebSocket 协议 |
实现复杂度 | 简单(浏览器原生支持) | 较复杂 |
重连机制 | 自带自动重连 | 需要手动实现 |
数据格式 | 文本(简单格式) | 二进制或文本 |
适用场景 | 通知、实时数据展示 | 聊天、游戏等双向交互 |
简单来说,如果你只需要服务器向客户端(单向通信)推送数据(如实时监控面板、新闻推送),SSE 是更轻量的选择;如果需要双向通信(如即时聊天),则应该选择 WebSocket。
三、基础使用:前端如何使用 SSE?
现代浏览器(除 IE 外)原生支持 SSE,通过EventSource
API 即可轻松使用:
// 建立连接
const source = new EventSource('/api/sse-endpoint');// 监听消息事件
source.onmessage = (event) => {console.log('收到数据:', event.data);// 处理数据...
};// 监听连接打开
source.onopen = () => {console.log('连接已建立');
};// 监听错误
source.onerror = (error) => {console.error('发生错误:', error);
};// 关闭连接(必要时)
// source.close();
SSE 还支持自定义事件类型,让我们可以更灵活地处理不同类型的消息:
// 监听自定义事件
source.addEventListener('temperatureUpdate', (event) => {console.log('温度更新:', event.data);
});source.addEventListener('systemMessage', (event) => {console.log('系统消息:', event.data);
});
四、实战演练
1、Express 后端实现 SSE
ps:如何创建express应用程序在本篇文章不会提及,有需要请阅读文章:
https://blog.csdn.net/orbit4/article/details/144678842
讲解如何在 Express 中实现 SSE 服务,主要分为以下几个步骤:
1. 设置正确的响应头
SSE 需要特定的 HTTP 头来告知客户端这是一个事件流:
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
2. 保持连接并发送数据
与普通 HTTP 请求不同,SSE 连接需要保持打开状态。我们使用res.write()
而不是res.send()
或res.end()
来发送数据:
// 存储所有客户端连接
let clients = [];router.get('/', (req, res) => {// 设置SSE响应头res.setHeader('Content-Type', 'text/event-stream');res.setHeader('Cache-Control', 'no-cache');res.setHeader('Connection', 'keep-alive');res.flushHeaders();// 将新客户端添加到列表clients.push(res);// 发送连接确认res.write(`event: connected\n`);res.write(`data: ${JSON.stringify({ message: '已建立SSE连接' })}\n\n`);// 处理客户端断开req.on('close', () => {clients = clients.filter(client => client !== res);});
});
3. 遵循 SSE 数据格式
服务器发送的每条消息必须遵循特定格式:
- 以
data:
开头,后跟实际内容 - 每条消息以两个换行符 (
\n\n
) 结束 - 可选指定
event:
字段来定义事件类型 - 可选指定
id:
字段来标识消息 - 可选指定
retry:
字段来建议重连间隔
// 定期向所有客户端发送数据
setInterval(() => {const data = {timestamp: new Date().toISOString(),temperature: (20 + Math.random() * 10).toFixed(2),humidity: (40 + Math.random() * 30).toFixed(2)};// 向每个连接的客户端发送数据clients.forEach(client => {client.write(`data: ${JSON.stringify(data)}\n\n`);});
}, 2000);
4. 处理跨域问题
由于我们的前端和后端可能运行在不同端口,需要配置 CORS:
//安装
npm install cors
const cors = require('cors');// 配置CORS允许所有来源
app.use(cors({origin: "*",methods: ["GET", "POST"],allowedHeaders: ["Content-Type"]
}));
2、Vue2 前端实现 SSE 客户端
在 Vue2 中使用 SSE,我们可以创建一个专门的组件来管理连接和处理数据:
1. 连接管理
在组件中,我们可以通过按钮控制 SSE 连接的建立和关闭:
methods: {toggleConnection() {if (this.isConnected) {this.disconnect();} else {this.connect();}},connect() {// 连接到后端SSE服务this.eventSource = new EventSource('http://localhost:3000/sse');// 监听消息事件this.eventSource.onmessage = (event) => {try {const data = JSON.parse(event.data);this.addMessage(data, false);this.startTypingEffect(this.messages.length - 1);} catch (error) {console.error('解析SSE数据失败:', error);}};// 监听连接打开和错误事件...},disconnect() {if (this.eventSource) {this.eventSource.close();this.eventSource = null;this.isConnected = false;}}
}
2. 实现打印机效果
为了让新消息以打字机效果展示,我们可以逐字显示文本并添加光标闪烁动画:
startTypingEffect(messageIndex) {const message = this.messages[messageIndex];if (message.isSystem) return;// 为每个字段启动打字效果Object.keys(message.content).forEach((field, fieldIndex) => {const text = message.content[field];const elementSelector = `.message-item:nth-child(${messageIndex + 1}) .data-field:nth-child(${fieldIndex + 1}) .typed-text`;this.$nextTick(() => {const element = this.$el.querySelector(elementSelector);if (element) {let i = 0;element.textContent = '';const typingInterval = setInterval(() => {if (i < text.length) {element.textContent += text.charAt(i);i++;} else {clearInterval(typingInterval);}}, 50); // 控制打字速度}});});
}
配合 CSS 实现光标闪烁效果:
.typed-text {border-right: 2px solid #333;padding-right: 3px;animation: blink 0.7s step-end infinite;
}@keyframes blink {from, to { border-color: transparent }50% { border-color: #333 }
}
3、案例效果
在控制台查看长连接实时传递数据
总结
SSE 是一种简单高效的服务器推送技术,非常适合只需要服务器向客户端单向发送数据的场景。与 WebSocket 相比,它实现简单且基于 HTTP 协议,无需额外的服务器配置。
本文只是一个简单的案例,有不足之处还请大家点出。