别再只用WebSocket了!用Node.js实现SSE(Server-sent events)给前端推消息,5分钟搞定一个实时通知功能
2026/4/19 13:25:37 网站建设 项目流程

5分钟用Node.js实现SSE实时通知:比WebSocket更轻量的选择

当我们需要在网页上实现实时通知功能时,WebSocket往往是第一个想到的技术方案。但你是否知道,对于只需要服务器向客户端单向推送数据的场景,有一个更简单、更轻量的替代方案?Server-Sent Events(SSE)正是为此而生,它基于标准HTTP协议,无需额外协议开销,实现起来异常简单。

想象这样一个场景:用户在你的电商网站下单后,需要实时看到订单状态的变化;或者在一个新闻网站上,编辑发布新文章时需要立即推送给所有在线用户。这些场景的共同特点是数据流动是单向的——从服务器到客户端。使用WebSocket来实现这些功能就像用大炮打蚊子,而SSE则提供了恰到好处的解决方案。

1. 为什么选择SSE而非WebSocket?

在实时通信领域,WebSocket和SSE各有其适用场景。让我们通过几个关键维度来对比这两种技术:

特性WebSocketSSE
通信方向双向单向(服务器→客户端)
协议基础独立协议基于HTTP
实现复杂度较高极低
自动重连需手动实现内置支持
浏览器兼容性优秀除IE外主流支持
适用场景聊天、游戏等通知、日志、状态更新

从表中可以看出,SSE在以下场景中具有明显优势:

  • 简单通知系统:站内消息、订单状态更新
  • 实时日志展示:构建日志监控面板
  • 新闻/资讯推送:向所有订阅用户广播新内容
  • 进度报告:长时间操作的状态更新

提示:SSE使用标准的HTTP协议,这意味着它可以无缝通过大多数防火墙和代理服务器,而WebSocket有时会遇到连接问题。

2. SSE技术核心解析

SSE的工作原理其实非常简单。它建立在HTTP协议之上,通过以下几个关键机制实现实时通信:

  1. 持久连接:客户端发起一个普通的HTTP GET请求,服务器保持这个连接开放
  2. 特殊MIME类型:服务器设置Content-Type: text/event-stream
  3. 数据格式:服务器按照特定格式发送事件流
  4. 自动重连:浏览器内置重连机制,连接中断时会自动尝试重新连接

一个基本的SSE数据包格式如下:

event: statusUpdate id: 12345 data: {"status":"shipped","orderId":"ORD-789"}

其中:

  • event: 定义事件类型(可选)
  • id: 消息标识符,用于断线重连时同步
  • data: 实际的消息内容(可以多行)
  • retry: 指定重连等待时间(毫秒)

3. Node.js实现SSE服务端

让我们通过一个完整的订单状态通知示例,来看看如何在Node.js中实现SSE服务端。我们将使用原生HTTP模块来保持示例的简洁性。

const http = require('http'); const { EventEmitter } = require('events'); // 创建事件发射器来管理订单状态更新 const orderEventEmitter = new EventEmitter(); const server = http.createServer((req, res) => { if (req.url === '/events') { // 设置SSE必需的响应头 res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }); // 发送初始连接消息 res.write('event: connected\n'); res.write('data: Welcome to order updates\n\n'); // 监听新订单状态事件 const sendUpdate = (order) => { res.write(`event: orderUpdate\n`); res.write(`id: ${order.id}\n`); res.write(`data: ${JSON.stringify(order)}\n\n`); }; orderEventEmitter.on('update', sendUpdate); // 客户端断开连接时清理 req.on('close', () => { orderEventEmitter.off('update', sendUpdate); }); } else { res.writeHead(404); res.end('Not found'); } }); server.listen(3000, () => { console.log('SSE server running on port 3000'); }); // 模拟订单状态更新 setInterval(() => { const mockOrder = { id: Date.now(), status: ['pending', 'processing', 'shipped', 'delivered'][Math.floor(Math.random() * 4)], updatedAt: new Date().toISOString() }; orderEventEmitter.emit('update', mockOrder); }, 3000);

这段代码做了以下几件事:

  1. 创建HTTP服务器并监听/events端点
  2. 设置SSE必需的响应头
  3. 使用EventEmitter来管理订单更新事件
  4. 每3秒模拟一个订单状态更新
  5. 在客户端断开连接时正确清理事件监听器

4. 前端集成与EventSource API

在前端,我们可以使用浏览器内置的EventSourceAPI来接收SSE事件。以下是一个完整的HTML示例:

<!DOCTYPE html> <html> <head> <title>订单状态跟踪</title> <style> .update { margin: 10px; padding: 10px; border: 1px solid #ddd; } .pending { background-color: #FFF3CD; } .processing { background-color: #CCE5FF; } .shipped { background-color: #D4EDDA; } .delivered { background-color: #F8F9FA; } </style> </head> <body> <h1>实时订单状态</h1> <div id="updates"></div> <script> const updatesContainer = document.getElementById('updates'); // 创建EventSource连接 const eventSource = new EventSource('/events'); // 处理连接建立事件 eventSource.addEventListener('connected', (e) => { console.log('SSE连接已建立'); }); // 处理订单更新事件 eventSource.addEventListener('orderUpdate', (e) => { const order = JSON.parse(e.data); const updateElement = document.createElement('div'); updateElement.className = `update ${order.status}`; updateElement.innerHTML = ` <strong>订单 #${order.id}</strong> <p>状态: ${order.status}</p> <p>更新时间: ${new Date(order.updatedAt).toLocaleString()}</p> `; updatesContainer.prepend(updateElement); }); // 处理错误事件 eventSource.onerror = (e) => { console.error('SSE连接错误:', e); }; </script> </body> </html>

前端实现的关键点:

  • 使用new EventSource(url)建立连接
  • 通过addEventListener监听特定事件类型
  • data字段包含实际的消息内容
  • 浏览器会自动处理连接断开和重连

5. 生产环境注意事项

虽然SSE实现简单,但在生产环境中部署时,还需要考虑以下几个关键因素:

连接管理与扩展性

  • 连接限制:浏览器对同一域名下的并发SSE连接数有限制(通常6个)
  • 负载均衡:需要确保同一用户的请求总是路由到同一后端实例
  • 心跳机制:定期发送注释消息(: heartbeat\n\n)保持连接活跃

性能优化技巧

// 在生产环境中,可以考虑以下优化措施 res.write(': heartbeat\n\n'); // 每30秒发送一次心跳 // 使用compression中间件压缩事件流 app.use(compression()); // 设置适当的retry时间 res.write('retry: 5000\n\n'); // 5秒重连间隔

安全考虑

  • CORS配置:确保正确设置跨域头
  • 认证机制:可以通过Cookie或Token验证客户端
  • HTTPS:生产环境必须使用加密连接

常见问题解决方案

  1. 代理服务器超时

    • 配置代理服务器增加超时时间
    • 或实现心跳机制保持连接活跃
  2. 数据格式错误

    • 确保每条消息以两个换行符(\n\n)结束
    • 对数据进行UTF-8编码
  3. 内存泄漏

    • 确保在连接关闭时移除所有事件监听器
    • 监控服务器内存使用情况

6. 进阶应用场景

SSE的应用不仅限于简单的通知系统。以下是一些更高级的应用场景:

实时数据分析面板

// 服务器端 setInterval(() => { const metrics = { timestamp: Date.now(), cpuUsage: Math.random() * 100, memoryUsage: Math.random() * 80 + 20 }; res.write(`data: ${JSON.stringify(metrics)}\n\n`); }, 1000);

多人协作编辑

// 当文档内容变更时 documentEventEmitter.on('change', (change) => { res.write(`event: documentChange\n`); res.write(`data: ${JSON.stringify(change)}\n\n`); });

实时搜索建议

searchInput.addEventListener('input', (e) => { fetch(`/search?q=${e.target.value}`) .then(response => response.json()) .then(results => { res.write(`event: searchResults\n`); res.write(`data: ${JSON.stringify(results)}\n\n`); }); });

与现有框架集成

对于Express.js用户,可以创建专门的SSE路由:

app.get('/stream', (req, res) => { res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 发送初始数据 res.write('data: Connected\n\n'); // 定期发送更新 const interval = setInterval(() => { res.write(`data: ${new Date().toISOString()}\n\n`); }, 1000); // 清理 req.on('close', () => { clearInterval(interval); }); });

在实际项目中,我发现SSE特别适合那些需要从服务器向客户端推送数据,但客户端不需要频繁向服务器发送请求的场景。它的实现简单性让人惊喜——我曾经用不到50行代码就实现了一个实时日志查看器,这在以前使用WebSocket时需要更多的代码和额外的库支持。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询