当前位置: 首页 > news >正文

服务器发送事件(Server-Sent Events,SSE)详解

引言

1.1 SSE 简介

服务器发送事件(Server-Sent Events,SSE)是一种允许服务器向浏览器推送实时更新的技术。与 WebSocket 不同,SSE 是单向的,即服务器向客户端推送数据。SSE 基于 HTTP 协议,实现简单且易于使用。

1.2 使用场景

  • 实时通知:如股票行情、新闻更新等。
  • 实时数据流:如日志监控、实时数据分析等。
  • 进度更新:如文件上传进度、任务进度等。

SSE 架构设计

2.1 核心组件

  • 服务器:负责生成和发送事件流。
  • 客户端:通过 EventSource 对象接收事件流。

2.2 数据格式

SSE 使用纯文本格式,每个事件包含一个或多个字段,常见的字段包括 dataeventidretry

2.3 连接管理

  • 持久连接:服务器保持与客户端的连接,持续发送事件。
  • 自动重连:客户端在连接断开后自动重连。

实现方法

3.1 服务器端实现

3.1.1 设置响应头

服务器需要设置 Content-Typetext/event-stream,并保持连接打开。

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

3.1.2 发送事件

服务器通过发送格式化的文本流来推送事件。

data: Hello, SSE!

data: This is a second event.
event: customEvent
id: 123
retry: 1000

示例代码(Node.js + Express)

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/sse', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // 发送初始事件
    res.write('data: Hello, SSE!\n\n');

    // 定期发送事件
    const intervalId = setInterval(() => {
        const event = {
            data: 'This is a second event',
            event: 'customEvent',
            id: '123',
            retry: 1000
        };
        res.write(`data: ${event.data}\n`);
        res.write(`event: ${event.event}\n`);
        res.write(`id: ${event.id}\n`);
        res.write(`retry: ${event.retry}\n\n`);
    }, 2000);

    // 处理客户端关闭连接
    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

3.2 客户端实现

3.2.1 创建 EventSource 对象

客户端使用 EventSource 对象连接到服务器。

const eventSource = new EventSource('http://example.com/sse');

3.2.2 处理事件

客户端可以监听不同的事件类型。

eventSource.onmessage = function(event) {
    console.log('New message:', event.data);
};

eventSource.addEventListener('customEvent', function(event) {
    console.log('Custom event:', event.data);
});

3.2.3 错误处理

客户端可以监听错误事件。

eventSource.onerror = function(err) {
    console.error('EventSource failed:', err);
};

示例代码(HTML + JavaScript)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Example</title>
</head>
<body>
    <h1>Server-Sent Events Example</h1>
    <div id="messages"></div>
    <script>
        const eventSource = new EventSource('http://localhost:3000/sse');

        eventSource.onmessage = function(event) {
            const messagesDiv = document.getElementById('messages');
            const newMessage = document.createElement('div');
            newMessage.textContent = 'New message: ' + event.data;
            messagesDiv.appendChild(newMessage);
        };

        eventSource.addEventListener('customEvent', function(event) {
            const messagesDiv = document.getElementById('messages');
            const newMessage = document.createElement('div');
            newMessage.textContent = 'Custom event: ' + event.data;
            messagesDiv.appendChild(newMessage);
        });

        eventSource.onerror = function(err) {
            console.error('EventSource failed:', err);
        };
    </script>
</body>
</html>

实际应用案例

4.1 实时通知系统

  • 场景:股票行情、新闻更新等。
  • 实现:服务器定期推送最新的股票价格或新闻内容到客户端。

示例代码(Node.js + Express)

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/sse/stock', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // 模拟股票行情更新
    const intervalId = setInterval(() => {
        const stockData = {
            symbol: 'AAPL',
            price: (Math.random() * 100).toFixed(2)
        };
        res.write(`data: ${JSON.stringify(stockData)}\n\n`);
    }, 5000);

    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

客户端代码(HTML + JavaScript)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Stock Price SSE</title>
</head>
<body>
    <h1>Stock Price Updates</h1>
    <div id="stock"></div>
    <script>
        const eventSource = new EventSource('http://localhost:3000/sse/stock');

        eventSource.onmessage = function(event) {
            const stockDiv = document.getElementById('stock');
            const stockData = JSON.parse(event.data);
            stockDiv.textContent = `Symbol: ${stockData.symbol}, Price: $${stockData.price}`;
        };

        eventSource.onerror = function(err) {
            console.error('EventSource failed:', err);
        };
    </script>
</body>
</html>

4.2 实时数据监控

  • 场景:日志监控、实时数据分析等。
  • 实现:服务器实时推送日志数据或分析结果到客户端。

示例代码(Node.js + Express)

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/sse/logs', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // 模拟日志数据
    const intervalId = setInterval(() => {
        const logData = {
            timestamp: new Date().toISOString(),
            message: `Log entry ${Math.floor(Math.random() * 100)}`
        };
        res.write(`data: ${JSON.stringify(logData)}\n\n`);
    }, 3000);

    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

客户端代码(HTML + JavaScript)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Log Monitoring SSE</title>
</head>
<body>
    <h1>Log Monitoring</h1>
    <div id="logs"></div>
    <script>
        const eventSource = new EventSource('http://localhost:3000/sse/logs');

        eventSource.onmessage = function(event) {
            const logsDiv = document.getElementById('logs');
            const logData = JSON.parse(event.data);
            const newLog = document.createElement('div');
            newLog.textContent = `Timestamp: ${logData.timestamp}, Message: ${logData.message}`;
            logsDiv.appendChild(newLog);
        };

        eventSource.onerror = function(err) {
            console.error('EventSource failed:', err);
        };
    </script>
</body>
</html>

4.3 进度更新

  • 场景:文件上传进度、任务进度等。
  • 实现:服务器在文件上传或任务执行过程中,定期推送进度信息到客户端。

示例代码(Node.js + Express)

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/sse/progress', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    let progress = 0;
    const intervalId = setInterval(() => {
        progress += 10;
        if (progress > 100) {
            clearInterval(intervalId);
            res.write('data: { "progress": 100, "status": "completed" }\n\n');
            res.end();
        } else {
            res.write(`data: { "progress": ${progress}, "status": "in progress" }\n\n`);
        }
    }, 1000);

    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

客户端代码(HTML + JavaScript)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Progress Update SSE</title>
</head>
<body>
    <h1>Progress Update</h1>
    <div id="progress"></div>
    <script>
        const eventSource = new EventSource('http://localhost:3000/sse/progress');

        eventSource.onmessage = function(event) {
            const progressDiv = document.getElementById('progress');
            const progressData = JSON.parse(event.data);
            progressDiv.textContent = `Progress: ${progressData.progress}%, Status: ${progressData.status}`;
        };

        eventSource.onerror = function(err) {
            console.error('EventSource failed:', err);
        };
    </script>
</body>
</html>

优缺点

5.1 优点

  • 简单易用:基于 HTTP 协议,实现简单。
  • 单向通信:服务器向客户端单向推送数据,适合实时通知场景。
  • 浏览器支持:现代浏览器普遍支持 EventSource

5.2 缺点

  • 单向通信:不支持客户端向服务器发送数据。
  • 跨域限制:需要处理跨域请求。
  • 浏览器兼容性:部分旧版浏览器不支持 EventSource

性能优化与最佳实践

6.1 性能优化

6.1.1 压缩传输

使用 Gzip 压缩数据,减少传输量。

server {
    gzip on;
    gzip_types text/event-stream;
}

6.1.2 连接管理

合理管理连接,避免频繁重连。

const eventSource = new EventSource('http://example.com/sse');

eventSource.onerror = function(err) {
    console.error('EventSource failed:', err);
    setTimeout(() => {
        eventSource.close();
        const newEventSource = new EventSource('http://example.com/sse');
        // 重新绑定事件处理程序
    }, 5000);
};

6.1.3 事件格式

优化事件格式,减少不必要的数据传输。

data: {"type":"stock","symbol":"AAPL","price":150.75}

6.2 最佳实践

6.2.1 错误处理

实现完善的错误处理机制,确保连接的稳定性。

eventSource.onerror = function(err) {
    console.error('EventSource failed:', err);
    eventSource.close();
    setTimeout(() => {
        const newEventSource = new EventSource('http://example.com/sse');
        // 重新绑定事件处理程序
    }, 5000);
};

6.2.2 跨域支持

配置 CORS(跨域资源共享)以支持跨域请求。

const express = require('express');
const cors = require('cors');
const app = express();
const PORT = 3000;

app.use(cors());

app.get('/sse', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    res.write('data: Hello, SSE!\n\n');

    const intervalId = setInterval(() => {
        const event = {
            data: 'This is a second event',
            event: 'customEvent',
            id: '123',
            retry: 1000
        };
        res.write(`data: ${event.data}\n`);
        res.write(`event: ${event.event}\n`);
        res.write(`id: ${event.id}\n`);
        res.write(`retry: ${event.retry}\n\n`);
    }, 2000);

    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

6.2.3 事件命名

使用有意义的事件名称,便于客户端处理。

event: stockUpdate
data: {"symbol":"AAPL","price":150.75}

总结

服务器发送事件(SSE)是一种简单且有效的技术,适用于需要从服务器向客户端推送实时更新的场景。通过本文的介绍,读者可以了解 SSE 的工作原理、使用场景、实现方法以及实际应用案例,从而更好地利用 SSE 提升应用的实时性和用户体验。

相关文章:

  • 【算法学习之路】11.并查集
  • 剑指 Offer II 107. 矩阵中的距离
  • 专线、云 和 物联网(IoT)
  • 【css酷炫效果】纯CSS实现进度条加载动画
  • LightRAG简要概述
  • cmake --build . --config Release和make是1个意思吗
  • SQLMesh系列教程:利用date_spine宏构建日期序列实践指南
  • 网络工程安全从入门到“入魂“教学案
  • CellOracle|基因扰动研究基因功能|基因调控网络+虚拟干预
  • 使用STM32CubeMX+DMA+空闲中断实现串口接收和发送数据(STM32G070CBT6)
  • 黑客攻击deepseek服务原理解析
  • 基于SpringBoot的“酒店管理系统”的设计与实现(源码+数据库+文档+PPT)
  • 东方通TongHttpServer:企业级服务代理中间件的卓越之选
  • 《自然》:陆地蒸散量研究的统计失误被撤回-空间加权平均的计算方法
  • Unity特效动态合批问题
  • LINUX驱动学习之IIC驱动-----以AP3216C为例
  • 【css酷炫效果】纯CSS实现按钮流光边框
  • [QT]深入理解Qt中的信号与槽机制
  • 什么是梯度方差和缩放因子
  • PrimeTime:timing_report_unconstrained_paths变量
  • 五一去哪儿| 追着花期去旅行,“赏花经济”绽放文旅新活力
  • 习近平在上海考察
  • 豆神教育:2024年净利润1.37亿元,同比增长334%
  • 深入贯彻中央八项规定精神学习教育中央指导组培训会议召开
  • 当初没有珍惜巴特勒的热火,被横扫出局后才追悔莫及
  • 一周人物|卡鲁等入围英国特纳奖,李学明新展中国美术馆