JavaScript Server-Sent Events
什么是 Server-Sent Events?
Server-Sent Events (SSE) 是一种允许服务器向客户端推送数据的 Web 技术。与 WebSocket 不同,SSE 是单向通信的 - 数据只能从服务器流向客户端。这种技术特别适用于需要服务器实时更新客户端,但不需要客户端向服务器发送数据的场景。
SSE 使用 HTTP 协议,这意味着它不需要特殊的协议支持,可以在现有的 Web 基础设施上工作。
SSE 与其他技术的对比
在了解 SSE 之前,我们先来看看它与其他技术的区别:
SSE 的特点
- 单向通信:服务器向客户端发送数据,客户端只能接收
- 自动重连:如果连接丢失,浏览器会自动尝试重新连接
- 基于 HTTP:不需要特殊的协议,兼容现有的代理和防火墙
- 事件 ID:支持为每个事件指定 ID,便于断线重连时恢复
- 自定义事件类型:可以定义多种事件类型,便于客户端处理不同类型的数据
客户端:如何使用 SSE
在客户端,我们使用 EventSource
接口来接收服务器发送的事件。
基本用法
// 创建 EventSource 实例,连接到服务器的 SSE 端点
const eventSource = new EventSource('/api/events');
// 监听从服务器发送的消息
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到数据:', data);
};
// 监听连接打开事件
eventSource.onopen = function() {
console.log('SSE 连接已建立');
};
// 监听错误
eventSource.onerror = function(error) {
console.error('SSE 连接出错:', error);
// 可以在这里处理重连逻辑,或者依赖 EventSource 的自动重连
};
自定义事件
服务器可以发送具有特定类型的事件,客户端可以针对不同类型的事件注册不同的处理函数:
const eventSource = new EventSource('/api/events');
// 监听名为 'update' 的事件
eventSource.addEventListener('update', function(event) {
const updateData = JSON.parse(event.data);
console.log('收到更新:', updateData);
});
// 监听名为 'alert' 的事件
eventSource.addEventListener('alert', function(event) {
const alertData = JSON.parse(event.data);
console.log('收到警报:', alertData);
});
关闭连接
当你不再需要 SSE 连接时,应该关闭它以释放资源:
// 关闭 SSE 连接
function closeConnection() {
eventSource.close();
console.log('SSE 连接已关闭');
}
服务器端:如何实现 SSE
下面我们来看看如何在服务器端实现 SSE。这里以 Node.js 和 Express 为例:
const express = require('express');
const app = express();
app.get('/api/events', (req, res) => {
// 设置 SSE 所需的响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 发送一条初始消息
res.write('data: {"message": "连接已建立"}\n\n');
// 创建一个定时器,每隔 3 秒发送一条消息
const intervalId = setInterval(() => {
const data = {
time: new Date().toISOString(),
value: Math.random()
};
// 发送普通消息
res.write(`data: ${JSON.stringify(data)}\n\n`);
// 发送带有事件类型的消息
res.write(`event: update\ndata: ${JSON.stringify({ status: "updated" })}\n\n`);
}, 3000);
// 当客户端断开连接时清除定时器
req.on('close', () => {
clearInterval(intervalId);
console.log('客户端关闭了连接');
});
});
app.listen(3000, () => {
console.log('服务器启动在 3000 端口');
});
SSE 消息格式
服务器发送的消息需要遵循特定的格式:
field: value\n
field: value\n
\n
常用的字段包括:
- data: 消息的内容
- event: 事件类型
- id: 事件的唯一标识符
- retry: 重连间隔(毫秒)
例如:
event: update
id: 123
data: {"status":"updated","time":"2023-05-10T12:34:56Z"}
每个消息结束时必须有两个换行符(\n\n)。如果 data 字段有多行,每行都需要以 "data: " 开头。
实际应用案例
案例 1: 实时股票价格更新
假设我们正在开发一个股票监控应用,需要实时显示股票价格变化:
客户端代码:
// 创建到股票价格 SSE 端点的连接
const stockUpdates = new EventSource('/api/stocks/live');
// 监听价格更新
stockUpdates.addEventListener('price-update', function(event) {
const stockData = JSON.parse(event.data);
// 更新 UI 显示
const stockElement = document.querySelector(`#stock-${stockData.symbol}`);
if (stockElement) {
// 更新价格
stockElement.querySelector('.price').textContent = stockData.price;
// 根据涨跌显示不同颜色
const changeElement = stockElement.querySelector('.change');
changeElement.textContent = stockData.change;
if (stockData.change > 0) {
changeElement.className = 'change positive';
} else if (stockData.change < 0) {
changeElement.className = 'change negative';
} else {
changeElement.className = 'change neutral';
}
}
});
服务器端代码(Node.js/Express):
app.get('/api/stocks/live', (req, res) => {
// 设置 SSE 响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 模拟股票数据源
const stocks = ['AAPL', 'GOOGL', 'MSFT', 'AMZN'];
const initialPrices = {
'AAPL': 150.0,
'GOOGL': 2800.0,
'MSFT': 300.0,
'AMZN': 3400.0
};
// 每秒更新一次股票价格
const intervalId = setInterval(() => {
// 随机选择一只股票
const symbol = stocks[Math.floor(Math.random() * stocks.length)];
const currentPrice = initialPrices[symbol];
// 模拟价格变化 (-1% 到 +1%)
const change = currentPrice * (Math.random() * 0.02 - 0.01);
initialPrices[symbol] += change;
// 发送价格更新事件
res.write(`event: price-update\n`);
res.write(`data: ${JSON.stringify({
symbol,
price: initialPrices[symbol].toFixed(2),
change: change.toFixed(2)
})}\n\n`);
}, 1000);
// 当客户端断开连接时清除定时器
req.on('close', () => {
clearInterval(intervalId);
});
});
案例 2: 实时通知系统
另一个常见的应用是实时通知系统,比如社交媒体平台上的新消息提醒:
客户端代码:
const notifications = new EventSource('/api/notifications');
// 处理不同类型的通知
notifications.addEventListener('new-message', event => {
const data = JSON.parse(event.data);
showNotification('新消息', `来自 ${data.sender}: ${data.preview}`);
});
notifications.addEventListener('friend-request', event => {
const data = JSON.parse(event.data);
showNotification('好友请求', `${data.name} 想添加你为好友`);
});
notifications.addEventListener('system', event => {
const data = JSON.parse(event.data);
showNotification('系统通知', data.message);
});
function showNotification(title, message) {
// 创建通知元素
const notification = document.createElement('div');
notification.className = 'notification';
notification.innerHTML = `
<h4>${title}</h4>
<p>${message}</p>
`;
// 添加到通知区域
document.getElementById('notification-area').appendChild(notification);
// 5秒后自动移除
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 500);
}, 5000);
}
SSE 的浏览器兼容性
SSE 在大多数现代浏览器中都得到了支持,但在 Internet Explorer 中不可用:
浏览器 | 兼容性 |
---|---|
Chrome | ✅ 支持 |
Firefox | ✅ 支持 |
Safari | ✅ 支持 |
Edge | ✅ 支持 |
Internet Explorer | ❌ 不支持 |
如果需要支持 Internet Explorer,可以使用 polyfill 库如 EventSource polyfill。
SSE 的优缺点
优点
- 简单易用:API 简单,容易实现
- 基于 HTTP:可以利用现有的 HTTP 基础设施
- 自动重连:浏览器会自动处理连接断开的情况
- 原生支持事件 ID:有利于断点续传
- 支持自定义事件:可以定义不同类型的事件
缺点
- 单向通信:只能从服务器向客户端发送数据
- 连接数限制:每个浏览器对于同一个域名的连接数有限制
- IE 不支持:需要使用 polyfill
- 无法发送二进制数据:只能发送文本数据
最佳实践
- 实现心跳机制:定期发送小型消息保持连接活跃
- 使用事件 ID:便于客户端在断线重连时恢复
- 设置合理的重连时间:避免在网络不稳定时过于频繁地重连
- 错误处理:在客户端和服务器端都实现错误处理逻辑
- 优化数据格式:只发送必要的数据,减少网络负载
// 服务器端发送心跳的例子
function startEventStream(res) {
let id = 0;
// 每 30 秒发送一次心跳
const heartbeat = setInterval(() => {
res.write(`:heartbeat\n\n`);
}, 30000);
// 每次发送数据时包含 ID
function sendEvent(event, data) {
id++;
res.write(`id: ${id}\n`);
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
// 处理连接关闭
req.on('close', () => {
clearInterval(heartbeat);
});
// 返回发送事件的函数
return sendEvent;
}
总结
Server-Sent Events 是一种强大而简单的技术,适用于从服务器向客户端推送实时更新。它比 WebSocket 更轻量,使用标准的 HTTP 协议,并具有自动重连和事件 ID 等便利功能。
SSE 特别适用于以下场景:
- 实时通知系统
- 股票或加密货币价格更新
- 社交媒体动态推送
- 实时数据仪表板
- 日志或事件监控
通过学习本文内容,你应该已经掌握了 SSE 的基本概念、客户端和服务器端的实现方法,以及实际应用案例。在接下来的项目中,当需要从服务器向客户端推送实时数据时,不妨考虑使用 SSE 这一高效而简洁的解决方案。
练习
- 创建一个简单的聊天室应用,使用 SSE 推送新消息。
- 实现一个天气更新应用,每隔一段时间从服务器获取最新天气情况。
- 构建一个简单的游戏排行榜,实时显示玩家分数变化。
- 尝试实现一个带有重连机制的 SSE 客户端,使用
Last-Event-ID
头部恢复连接。