Skip to content

HTML5 - Server-Sent Events

Server-Sent Events (SSE) 是一种标准,使 Web 服务器能够通过单个、持久的 HTTP 连接,将数据更新推送到 Web 客户端(浏览器)。与 WebSockets(双向)不同,SSE 是单向的:数据只从服务器流向客户端。

这使得 SSE 成为 WebSockets 的更简单替代方案,特别是在你只需服务器向客户端发送更新的场景下,例如实时新闻源、股票行情、状态更新、通知或监控面板。

SSE 使用标准的 HTTP 连接和特定的基于文本的事件流格式,这使得它在某些服务器基础设施上可能比 WebSockets 更易于实现,并且在可能干扰 WebSocket 连接的代理中更具弹性。

在客户端,你使用 EventSource 接口来连接到 SSE 端点。

// 创建一个 EventSource 实例,连接到服务器端点
const source = new EventSource('/sse-endpoint'); // 替换为你的实际端点 URL

如果连接丢失,浏览器将自动尝试重新连接。

你使用附加到 EventSource 对象的事件监听器来监听消息:

  • onopen: 当与服务器的连接成功建立时触发。
  • onmessage: 当从服务器接收到没有特定事件名称的消息时触发。事件对象的 data 属性包含消息载荷。
  • onerror: 如果发生错误(例如,连接失败)时触发。注意:初始时不会对像 404 或 500 这样的 HTTP 错误状态码触发,只有在连接断开时才会触发。
  • 自定义事件:如果服务器发送带有特定名称的事件(使用 event: 字段),你可以使用 addEventListener('eventName', handler) 来监听它们。
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events Example</title>
</head>
<body>
<h1>Server Time Updates (SSE)</h1>
<div id="timeDisplay">Waiting for server time...</div>
<div id="status">Connecting...</div>
<script>
const timeDisplay = document.getElementById('timeDisplay');
const statusDiv = document.getElementById('status');
// 检查浏览器支持
if (typeof(EventSource) !== "undefined") {
// 将 '/get-time-sse' 替换为你的实际服务器端点
const source = new EventSource('/get-time-sse');
statusDiv.textContent = 'Connecting to event source...';
// 连接打开时调用
source.onopen = function(event) {
statusDiv.textContent = 'Connection opened!';
console.log("SSE Connection opened.", event);
};
// 对于没有特定事件名称的消息调用(默认)
source.onmessage = function(event) {
// 假设服务器以纯文本形式在数据字段中发送时间
timeDisplay.textContent = `Server time: ${event.data}`;
console.log("SSE Message received:", event.data);
};
// 示例:监听自定义命名事件 'userUpdate'
source.addEventListener('userUpdate', function(event) {
console.log("Custom 'userUpdate' event received:", event.data);
// 处理用户更新数据(例如,解析 JSON)
// const userData = JSON.parse(event.data);
// updateUI(userData);
}, false);
// 发生错误时调用(例如,连接丢失)
source.onerror = function(event) {
statusDiv.textContent = 'Connection error. Reconnecting...';
console.error("SSE Error occurred:", event);
// 浏览器将自动尝试重新连接。
// 如果服务器明确关闭连接(例如,HTTP 204),
// 或者如果 readyState 变为 CLOSED,你可能想要停止重新连接:
if (source.readyState === EventSource.CLOSED) {
statusDiv.textContent = 'Connection closed permanently by server.';
source.close(); // 确保客户端也关闭它
}
};
} else {
statusDiv.textContent = 'Sorry, your browser does not support Server-Sent Events.';
}
</script>
</body>
</html>

你可以使用 close() 方法从客户端明确关闭连接:

// 关闭连接
source.close();
console.log("SSE connection closed by client.");

服务器端脚本需要:

  • 设置 Content-Type 头部为 text/event-stream。
  • 设置适当的缓存头部(例如,Cache-Control: no-cache)以防止中间代理缓存响应。
  • 保持连接打开。
  • 根据 SSE 规范格式化数据并发送。

消息是纯文本,由两个换行符 (\n\n) 分隔。每条消息可以包含一个或多个字段,每个字段以单个换行符 (\n) 结束。常见的字段有:

  • data: <消息内容>:事件的实际数据载荷。如果每行都以 data: 开头,则可以跨多行。
  • event: <事件名称>:指定事件的名称。如果省略,消息将在客户端触发 onmessage 处理器。
  • id: <唯一ID>:为事件设置一个唯一的 ID。如果连接中断,浏览器将发送一个带有最后接收到的 ID 的 Last-Event-ID HTTP 头部,允许服务器恢复流。
  • retry: <毫秒>:指定连接丢失时客户端应等待多长时间(以毫秒为单位)再尝试重新连接。

这个简单的 Node.js 示例每秒发送一次当前服务器时间。

// server.js (Node.js 示例)
const http = require('http');
const server = http.createServer((req, res) => {
// SSE 端点的路由
if (req.url === '/get-time-sse' && req.method === 'GET') {
// 设置 SSE 的头部
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive', // 重要:保持连接打开
'Access-Control-Allow-Origin': '*' // 可选:如果需要,添加 CORS 头部
});
console.log('Client connected to SSE endpoint.');
// 立即发送一条初始消息(可选)
res.write(`data: Server connected at ${new Date().toLocaleTimeString()}\n\n`);
// 每秒发送时间更新
const intervalId = setInterval(() => {
const currentTime = new Date().toLocaleTimeString();
// 简单消息(触发 'onmessage')
res.write(`data: ${currentTime}\n\n`);
// 发送命名事件和 ID 的示例
// res.write(`id: ${Date.now()}\n`);
// res.write(`event: serverTimeUpdate\n`);
// res.write(`data: ${JSON.stringify({ time: currentTime })}\n\n`);
}, 1000);
// 处理客户端断开连接
req.on('close', () => {
console.log('Client disconnected from SSE endpoint.');
clearInterval(intervalId); // 停止发送更新
res.end(); // 结束响应
});
} else {
// 处理其他请求(例如,提供 HTML 页面)
res.writeHead(404);
res.end('Not Found');
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});

要运行此代码,请将其保存为 server.js 并运行 node server.js。然后,访问 HTML 页面(你需要单独提供该页面或添加另一个路由),并将其 EventSource URL 更新为 http://localhost:3000/get-time-sse。

  • 方向性: SSE 仅从服务器到客户端;WebSockets 是双向的。
  • 协议: SSE 使用标准的 HTTP(S);WebSockets 在初始 HTTP 握手后使用不同的协议(ws/wss)。
  • 复杂度: SSE 通常被认为实现更简单,尤其是在客户端。
  • 开销: SSE 通常比 WebSockets 的每条消息协议开销更少。
  • 代理/防火墙兼容性: SSE 通常能更好地穿透可能阻止 WebSocket 连接的限制性代理。
  • 内置特性: SSE 内置支持自动重连和事件 ID,这些在 WebSockets 中需要手动实现。

当你主要需要从服务器向客户端推送更新时,选择 SSE。需要完整的实时双向通信时,选择 WebSockets。