2. 使用Server-Sent Events实现实时数据流
任务说明
本案例要求使用 Server-Sent Events (SSE) 实现一个实时数据流服务:服务端持续生成模拟的传感器温度数据,通过 SSE 协议推送给浏览器客户端,前端页面实时更新显示最新温度值及历史曲线。完成目标是得到一个可直接运行的前后端一体化应用,验证 SSE 单向实时推送能力。
实现方案
采用 Node.js 原生 http 模块实现 SSE 服务端,无需第三方依赖。服务端每 2 秒生成一个随机温度值(20°C~30°C),通过 text/event-stream 响应推送给客户端。前端使用原生 EventSource API 接收数据,并利用 Canvas 绘制最近 20 个数据点的温度曲线。整个应用包含一个 HTML 文件和一个 JS 文件,通过 node server.js 启动后访问 http://localhost:3000 即可查看效果。
代码实现
server.js — SSE 服务端与静态页面托管:
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = 3000;
// 生成模拟温度数据
function generateTemperature() {
return (20 + Math.random() * 10).toFixed(1);
}
const server = http.createServer((req, res) => {
// 路由:SSE 数据流
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
// 发送初始注释,建立连接
res.write(': connected\n\n');
// 每 2 秒推送一条温度数据
const timer = setInterval(() => {
const temp = generateTemperature();
const data = `data: ${JSON.stringify({ time: Date.now(), value: temp })}\n\n`;
res.write(data);
}, 2000);
// 客户端断开时清理定时器
req.on('close', () => {
clearInterval(timer);
res.end();
});
return;
}
// 路由:静态页面
if (req.url === '/' || req.url === '/index.html') {
fs.readFile(path.join(__dirname, 'index.html'), (err, content) => {
if (err) {
res.writeHead(500);
res.end('Server Error');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content);
});
return;
}
// 其他请求返回 404
res.writeHead(404);
res.end('Not Found');
});
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
index.html — 前端实时展示页面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SSE 实时温度监控</title>
</head>
<body>
<h3>实时温度监控(SSE)</h3>
<p>当前温度:<span id="currentTemp">--</span> °C</p>
<canvas id="chart" width="600" height="200"></canvas>
<script>
const tempSpan = document.getElementById('currentTemp');
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const MAX_POINTS = 20;
let dataPoints = [];
// 绘制温度曲线
function drawChart() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#2196F3';
ctx.lineWidth = 2;
ctx.beginPath();
const stepX = canvas.width / (MAX_POINTS - 1);
dataPoints.forEach((point, index) => {
const x = index * stepX;
const y = canvas.height - ((point - 15) / 20) * canvas.height;
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
// 绘制坐标轴标签
ctx.fillStyle = '#333';
ctx.font = '12px sans-serif';
ctx.fillText('20°C', 5, 20);
ctx.fillText('30°C', 5, canvas.height - 10);
}
// 建立 SSE 连接
const eventSource = new EventSource('/events');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
tempSpan.textContent = data.value;
dataPoints.push(parseFloat(data.value));
if (dataPoints.length > MAX_POINTS) {
dataPoints.shift();
}
drawChart();
};
eventSource.onerror = function() {
tempSpan.textContent = '连接断开';
};
</script>
</body>
</html>
SSE 通信机制
运行与验证
运行步骤:
- 将上述两个文件保存至同一目录(
server.js与index.html); - 在终端执行
node server.js; - 打开浏览器访问
http://localhost:3000。
验证方法与判定标准:
- 页面打开后,“当前温度”显示数值,且每约 2 秒自动更新一次;
- Canvas 区域绘制出随时间向右延伸的温度曲线,数据点不超过 20 个时曲线从左向右增长,达到 20 个后保持滑动窗口效果;
- 打开浏览器开发者工具 → Network 面板,筛选
events请求,确认响应头Content-Type为text/event-stream,且持续收到data:格式的消息; - 关闭页面标签页后,服务器终端不会报错,定时器正常清理。
预期输出示例(根据代码逻辑可唯一推出):
data: {"time":1710000000000,"value":24.5}
data: {"time":1710000002000,"value":27.8}
data: {"time":1710000004000,"value":22.1}
其中 time 为实际时间戳,value 为 20.0~30.0 之间的随机数,每次运行结果不同。