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 通信机制

SSE 单向实时推送时序 浏览器客户端 EventSource API Node.js 服务器 /events 端点 HTTP GET /events (1) 发起连接 Content-Type: text/event-stream (2) 返回 SSE 响应头 data: {"time": 1710000000000, "value": 24.5} (3) 每 2 秒推送一条温度数据 data: {"time": 1710000002000, "value": 27.8} data: {"time": 1710000004000, "value": 22.1} (4) 持续推送... 客户端关闭 → 服务器 clearInterval 并断开连接

运行与验证

运行步骤:

  1. 将上述两个文件保存至同一目录(server.js 与 index.html);
  2. 在终端执行 node server.js;
  3. 打开浏览器访问 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 之间的随机数,每次运行结果不同。