🏠 首页 攻略 SSE 服务端事件实时推送教程:比 WebSocket 更轻量的实时通信方案

SSE 服务端事件实时推送教程:比 WebSocket 更轻量的实时通信方案

SSE(Server-Sent Events)是基于 HTTP 的单向实时推送技术。本文从原理到实战,带你掌握 SSE 的配置方法,对比 WebSocket 选择最适合的实时通信方案。

实时通信不只是 WebSocket

提到实时通信,很多人脑子里蹦出来的第一个词就是 WebSocket。

但你知道吗?WebSocket 只是实时通信的其中一种方案。如果你只需要服务器单方面推送消息给客户端,SSE(Server-Sent Events)可能是更好的选择。

SSE 基于标准的 HTTP 协议,不需要特殊的握手过程,浏览器原生支持,而且自带重连机制。对于新闻推送、股票行情、监控告警这类单向通信场景,SSE 比 WebSocket 简洁得多。

SSE 是什么?

SSE 的全称是 Server-Sent Events,是 HTML5 规范的一部分。它允许服务器主动向客户端推送数据,而且完全建立在 HTTP 之上。

你可以把它理解成一个持久化的 HTTP 连接。客户端发起请求后,服务器不关闭连接,而是持续往里面塞数据。客户端收到数据后触发事件回调。

SSE 的核心特点:

  • 单向推送:只能服务器发给客户端,客户端用普通 HTTP POST 发消息
  • 文本格式:默认传输文本数据(JSON 也可以),不需要序列化
  • 自动重连:连接断开后浏览器自动尝试重连
  • 事件类型:支持自定义事件名称,客户端按类型处理
  • 跨域支持:天然支持 CORS,不像 WebSocket 需要额外配置

前端使用 SSE

前端使用 SSE 非常简单,只需要几行代码:

const eventSource = new EventSource('/api/stream');

eventSource.onopen = function() {
  console.log('连接已建立');
};

eventSource.onmessage = function(event) {
  const data = JSON.parse(event.data);
  console.log('收到消息:', data);
};

// 监听自定义事件
eventSource.addEventListener('update', function(event) {
  console.log('更新事件:', event.data);
});

// 错误处理
eventSource.onerror = function(error) {
  console.error('连接出错:', error);
};

EventSource 是浏览器内置的 API,Chrome、Firefox、Safari、Edge 全部支持。不需要引入任何第三方库。

后端实现 SSE

Node.js 示例

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

app.get('/api/stream', (req, res) => {
  // 设置 SSE 响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders(); // 把响应头先发送出去

  let id = 0;

  // 每秒推送一条数据
  const interval = setInterval(() => {
    id++;
    const data = JSON.stringify({
      id: id,
      time: new Date().toISOString(),
      message: `第 ${id} 条推送`
    });
    
    res.write(`id: ${id}\n`);
    res.write(`data: ${data}\n\n`);
  }, 1000);

  // 客户端断开时清理定时器
  req.on('close', () => {
    clearInterval(interval);
  });
});

app.listen(3000);

关键点有三个:

Content-Type 必须是 text/event-stream。这是 SSE 的协议标识,告诉浏览器这是一个 SSE 连接。

不能缓存。加上 Cache-Control: no-cacheConnection: keep-alive,确保连接一直开着。

数据格式要规范。每条消息以 data: 开头,两条消息之间用两个换行符分隔。

Python 示例(FastAPI)

from fastapi import FastAPI
import asyncio
import json
from datetime import datetime

app = FastAPI()

@app.get("/api/stream")
async def stream():
    async def event_stream():
        counter = 0
        while True:
            counter += 1
            yield f"id: {counter}\n"
            yield f"data: {json.dumps({'time': str(datetime.now()), 'count': counter})}\n\n"
            await asyncio.sleep(2)
    
    from starlette.responses import StreamingResponse
    return StreamingResponse(event_stream(), media_type="text/event-stream")

Go 示例

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

type Message struct {
	ID    int       `json:"id"`
	Time  time.Time `json:"time"`
	Data  string    `json:"data"`
}

func streamHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}
	
	counter := 0
	ticker := time.NewTicker(2 * time.Second)
	defer ticker.Stop()
	
	for range ticker.C {
		counter++
		msg := Message{
			ID:   counter,
			Time: time.Now(),
			Data: fmt.Sprintf("第 %d 条消息", counter),
		}
		data, _ := json.Marshal(msg)
		fmt.Fprintf(w, "id: %d\ndata: %s\n\n", counter, data)
		flusher.Flush()
	}
}

func main() {
	http.HandleFunc("/api/stream", streamHandler)
	http.ListenAndServe(":3000", nil)
}

SSE vs WebSocket 怎么选?

很多人纠结用哪个。其实答案很简单:看需求。

场景推荐方案原因
服务器推送到客户端SSE实现简单,自带重连
双向实时通信WebSocketSSE 不支持客户端主动推送
高并发推送SSE基于 HTTP,容易做负载均衡
游戏实时对战WebSocket低延迟,双向通信
聊天室WebSocket需要互相收发消息
股票行情推送SSE单向推送,重连友好
监控告警通知SSE简单可靠,运维友好

记住一个原则:如果只需要服务器单向推送,优先选 SSE。如果需要双向通信,才考虑 WebSocket。

SSE 的局限性和注意事项

SSE 不是万能的,它有几个明显的限制:

第一,只能文本数据。 SSE 传输的是纯文本,没法传二进制文件。如果你想推送图片或者大文件,得走 WebSocket 或者轮询。

第二,连接数有限制。 浏览器对同一个域名同时建立的 HTTP 连接数量有限制,一般是 6 个。SSE 占用一个连接,如果页面里开了太多 SSE 连接,可能会影响其他 HTTP 请求。

第三,不适合极低延迟场景。 SSE 基于 HTTP 长连接,虽然比轮询快,但延迟还是比 WebSocket 高一些。游戏或者高频交易场景不建议用 SSE。

第四,需要处理连接稳定性。 虽然 SSE 自带重连机制,但中间如果有 Nginx 或者 CDN 代理,超时设置可能会意外断开连接。记得把代理的超时时间调大,或者直接关掉代理的缓冲。

Nginx 配置 SSE 时要注意:

location /api/stream {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
}

proxy_buffering off 是关键配置。不开缓冲的话,Nginx 不会等后端返回完整响应再转发,而是立即把数据推送给客户端。

实际应用场景

实时通知系统:后台管理系统里的待办事项提醒、审批通知,用 SSE 推送非常合适。用户不用刷新页面,新消息实时出现在右上角。

数据仪表盘:监控大屏展示 CPU 使用率、内存占用、请求量等指标。服务器每秒推送最新数据,前端实时更新图表。

进度条反馈:文件上传、模型训练、数据处理这些耗时操作,用 SSE 实时推送进度百分比,用户体验比轮询好太多了。

多人协作编辑:虽然最终方案可能是 WebSocket,但在只需要单向同步的场景下,SSE 也能胜任。比如协作文档里的只读预览。

总结

SSE 是一个被低估的技术。它没有 WebSocket 那么炫酷,但胜在简单、稳定、兼容性好。

如果你的场景是服务器单向推送数据,SSE 几乎是最优解。几行代码就能跑起来,不需要额外的基础设施,浏览器原生支持,还能利用现有的 HTTP 缓存和代理机制。

下次再做实时功能的时候,不妨先想想:我真的需要 WebSocket 吗?也许 SSE 就够了。