别再只用WebSocket了!用Node.js实现SSE(Server-sent events)给前端推消息,5分钟搞定一个实时通知功能

张开发
2026/4/19 13:25:37 15 分钟阅读

分享文章

别再只用WebSocket了!用Node.js实现SSE(Server-sent events)给前端推消息,5分钟搞定一个实时通知功能
5分钟用Node.js实现SSE实时通知比WebSocket更轻量的选择当我们需要在网页上实现实时通知功能时WebSocket往往是第一个想到的技术方案。但你是否知道对于只需要服务器向客户端单向推送数据的场景有一个更简单、更轻量的替代方案Server-Sent EventsSSE正是为此而生它基于标准HTTP协议无需额外协议开销实现起来异常简单。想象这样一个场景用户在你的电商网站下单后需要实时看到订单状态的变化或者在一个新闻网站上编辑发布新文章时需要立即推送给所有在线用户。这些场景的共同特点是数据流动是单向的——从服务器到客户端。使用WebSocket来实现这些功能就像用大炮打蚊子而SSE则提供了恰到好处的解决方案。1. 为什么选择SSE而非WebSocket在实时通信领域WebSocket和SSE各有其适用场景。让我们通过几个关键维度来对比这两种技术特性WebSocketSSE通信方向双向单向(服务器→客户端)协议基础独立协议基于HTTP实现复杂度较高极低自动重连需手动实现内置支持浏览器兼容性优秀除IE外主流支持适用场景聊天、游戏等通知、日志、状态更新从表中可以看出SSE在以下场景中具有明显优势简单通知系统站内消息、订单状态更新实时日志展示构建日志监控面板新闻/资讯推送向所有订阅用户广播新内容进度报告长时间操作的状态更新提示SSE使用标准的HTTP协议这意味着它可以无缝通过大多数防火墙和代理服务器而WebSocket有时会遇到连接问题。2. SSE技术核心解析SSE的工作原理其实非常简单。它建立在HTTP协议之上通过以下几个关键机制实现实时通信持久连接客户端发起一个普通的HTTP GET请求服务器保持这个连接开放特殊MIME类型服务器设置Content-Type: text/event-stream数据格式服务器按照特定格式发送事件流自动重连浏览器内置重连机制连接中断时会自动尝试重新连接一个基本的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);这段代码做了以下几件事创建HTTP服务器并监听/events端点设置SSE必需的响应头使用EventEmitter来管理订单更新事件每3秒模拟一个订单状态更新在客户端断开连接时正确清理事件监听器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 idupdates/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生产环境必须使用加密连接常见问题解决方案代理服务器超时配置代理服务器增加超时时间或实现心跳机制保持连接活跃数据格式错误确保每条消息以两个换行符(\n\n)结束对数据进行UTF-8编码内存泄漏确保在连接关闭时移除所有事件监听器监控服务器内存使用情况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时需要更多的代码和额外的库支持。

更多文章