sse如何服务器推送

联启 网络工具 13

本文目录导读:

sse如何服务器推送-第1张图片-电脑手机工具软件下载 - 免费实用工具合集 | 联启科技

  1. SSE 的工作原理
  2. SSE 数据格式
  3. 服务端实现(代码示例)
  4. 客户端实现(浏览器 + JavaScript)
  5. 常用场景与优缺点
  6. 常见问题与注意事项
  7. 与 WebSocket 的对比

SSE(Server-Sent Events,服务器推送事件)是一种基于 HTTP 的轻量级技术,允许服务器主动向客户端(通常是浏览器)推送数据,相比 WebSocket,它更简单,且原生支持文本数据。

下面我会从原理服务端实现(Node.js/Python/Java)客户端实现常见问题四个角度详细讲解。


SSE 的工作原理

  1. 客户端发起请求:客户端通过 EventSource API 向服务器发送一个普通的 HTTP GET 请求。
  2. 服务器保持连接:服务器收到请求后,将响应头设置为 Content-Type: text/event-stream,并保持连接不关闭
  3. 持续推送数据:服务器可以随时向这个打开的连接写入文本数据,格式遵循 SSE 协议(见下文)。
  4. 客户端接收:浏览器的 EventSource 对象自动解析服务器推送的数据流,并触发 onmessageon 事件。

关键点:SSE 是单向的(服务器 -> 客户端),如果客户端需要向服务器发送数据,需要使用普通的 HTTP 请求(fetch 或 XHR)。


SSE 数据格式

SSE 的数据是纯文本,每行一个字段,以 \n 换行,主要字段:

  • event: 事件类型,默认是 message
  • data: 数据内容,可以有多行,以 \n 拼接。
  • id: 事件 ID,用于断线重连时发送 Last-Event-ID 头。
  • retry: 重连超时时间(毫秒),告诉浏览器如果连接断开,多久后重试。
  • 空行(\n\n):表示一个事件结束。

示例:

event: userupdate
data: {"username": "Alice", "status": "online"}
event: message
data: line1
data: line2
: 这个是注释行,以冒号开头,客户端会忽略

服务端实现(代码示例)

Node.js (使用原生 http 或 Express)

const express = require('express');
const app = express();
// SSE 端点
app.get('/events', (req, res) => {
    // 1. 设置 SSE 必需的头
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Access-Control-Allow-Origin': '*' // 如果有跨域
    });
    // 2. 模拟每隔 2 秒推送一次数据
    const intervalId = setInterval(() => {
        const data = {
            time: new Date().toLocaleTimeString(),
            value: Math.random()
        };
        // 格式:event + data + \n\n
        res.write(`event: message\n`);
        res.write(`data: ${JSON.stringify(data)}\n\n`);
    }, 2000);
    // 3. 客户端断开连接时,清理资源
    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
        console.log('Client disconnected');
    });
});
app.listen(3000, () => console.log('SSE server on http://localhost:3000/events'));

Python (使用 Flask)

from flask import Flask, Response, stream_with_context
import time
app = Flask(__name__)
@app.route('/events')
def sse():
    def generate():
        while True:
            data = {
                'time': time.strftime('%H:%M:%S'),
                'value': time.time()
            }
            yield f"event: message\ndata: {data}\n\n"
            time.sleep(2)
    headers = {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Access-Control-Allow-Origin': '*'
    }
    return Response(stream_with_context(generate()), headers=headers)
if __name__ == '__main__':
    app.run(port=5000, debug=False)

注意debug=True 会导致 Flask 开发服务器使用线程延迟,可能影响 SSE,生产环境请使用 Gunicorn 等。

Java (Spring Boot)

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.time.LocalTime;
@RestController
public class SSEController {
    @GetMapping(value = "/events", produces = "text/event-stream; charset=UTF-8")
    public Flux<String> sse() {
        return Flux.interval(Duration.ofSeconds(2))
            .map(seq -> {
                String data = "{\"time\": \"" + LocalTime.now() + "\", \"value\": " + Math.random() + "}";
                return "event: message\ndata: " + data + "\n\n";
            });
    }
}

依赖:需要 Spring WebFlux(spring-boot-starter-webflux)。


客户端实现(浏览器 + JavaScript)

// 1. 创建 EventSource 实例,连接到 SSE 端点
const eventSource = new EventSource('/events');
// 2. 监听 'message' 事件(默认事件类型)
eventSource.onmessage = function(event) {
    console.log('Received message:', event.data);
    // 解析 JSON 数据
    try {
        const data = JSON.parse(event.data);
        document.getElementById('output').innerHTML += `<div>${data.time} - ${data.value}</div>`;
    } catch(e) {
        console.error('Parse error', e);
    }
};
// 3. 监听自定义事件('userupdate')
eventSource.addEventListener('userupdate', function(event) {
    console.log('User update:', event.data);
});
// 4. 错误处理(连接断开或服务器故障)
eventSource.onerror = function(event) {
    console.error('EventSource failed:', event);
    // 浏览器默认会尝试重连(根据 retry 字段)
};
// 5. 断开连接(例如页面卸载时)
// eventSource.close();

关键点

  • new EventSource(url) 会自动发送 Accept: text/event-stream 头。
  • 客户端不能通过 EventSource 向服务器发送自定义请求头或 POST 数据。
  • 断线重连是自动进行的,浏览器会记录 Last-Event-ID 并在重连时发送给服务器。

常用场景与优缺点

适合场景

  • 实时通知:消息推送、系统告警、订单状态更新。
  • 实时数据流:股票价格、加密货币、新闻瀑布流。
  • 日志实时显示:服务器日志流、监控数据。
  • 自动刷新数据:仪表盘、统计面板。

优点

  • 简单:基于 HTTP,无需额外协议或库。
  • 浏览器原生支持:EventSource API,不需要额外的网络库。
  • 自动重连:浏览器自动处理,服务端只需发送 idretry
  • 与 HTTP/2 完美配合:可以复用同一个连接,无同源限制。

缺点

  • 单向通信:客户端不能通过 SSE 发送数据。
  • 浏览器限制:每个域名最多 6 个 SSE 连接(HTTP/1.1 下)。
  • 仅文本:只能发送文本数据(可发送 JSON 文本),不支持二进制。
  • IE 不支持:Edge 支持,但 IE 完全不行,需要 polyfill(如 eventsource-polyfill)。

常见问题与注意事项

  1. 代理服务器(Nginx 等)缓冲问题

    • Nginx 默认会缓冲响应,导致 SSE 数据延迟推送。
    • 需要在 Nginx 配置中关闭缓冲:
      location /events {
          proxy_pass http://backend;
          proxy_buffering off;   # 关闭缓冲
          proxy_cache off;       # 关闭缓存
          chunked_transfer_encoding on; # 开启分块传输
          proxy_set_header Connection '';
          proxy_http_version 1.1;
          proxy_redirect off;
      }
  2. 性能考虑

    每个客户端需要维护一个长连接,如果客户端数量很大(>5000),建议使用事件总线 + 消息队列,并通过反向代理(如 Nginx)来管理连接。

  3. 服务器超时

    • 有些代理或负载均衡器会超时空闲连接,使用 retry 字段或定期发送心跳(如每 30 秒发送一个 注释行)来保持连接活跃。
  4. 同源策略

    • 默认不支持跨域,如果需要跨域,服务器必须设置 Access-Control-Allow-Origin 头,并且客户端可能无法在跨域下发送 withCredentials(需显式设置)。

与 WebSocket 的对比

特性 SSE WebSocket
协议 HTTP 自定义协议(ws/wss)
通信方向 单向(服务器->客户端) 双向
数据类型 文本 文本 + 二进制
浏览器兼容性 大部分现代浏览器(IE 除外) 所有现代浏览器
自动重连 内置 需要自己实现
简单性 非常易用 稍复杂(握手、帧协议)

选择建议

  • 只需要服务器推送数据:选 SSE(更简单)。
  • 需要实时双向通信(如聊天、游戏):选 WebSocket。
  • 需要传输二进制数据(如文件、音频流):选 WebSocket。
  • 需要与现有 HTTP 基础设施集成:选 SSE。

标签: Sent Events) 服务端推送

抱歉,评论功能暂时关闭!