背景

在 @learning-labs 里,我整理了一套 SSE(Server-Sent Events)的学习与实践内容,目标是用最小完整例子把「概念、实现、测试」贯通起来,并对比它和 WebSocket 的取舍边界。

这一专题放在 learning-labs/sse/,源码地址:
https://github.com/wfnking/learning-labs/tree/master/sse

包含:

  • go-example/:Go 语言的完整 SSE 服务端 + 浏览器测试页面
  • node-example/:Node.js 命令行 SSE 客户端
  • README.md:概念、格式、实现要点与使用场景

SSE 的核心特点(实践视角)

  1. 单向推送:服务器 → 客户端,适合通知、日志、进度条等场景
  2. 基于 HTTPContent-Type: text/event-stream,无需额外协议
  3. 自动重连:浏览器 EventSource 内置重连,支持 retryLast-Event-ID
  4. 命名事件:通过 event: 字段区分不同事件类型

当你只需要服务端推送而不需要双向实时交互时,SSE 会比 WebSocket 更简单、可维护。


实现要点:服务端

1) 响应头必须正确

w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")

2) 消息格式统一

func formatSSEMessage(event, data string) string {
    if event != "" {
        return fmt.Sprintf("event: %s\ndata: %s\n\n", event, data)
    }
    return fmt.Sprintf("data: %s\n\n", data)
}

3) 保持连接 + 及时 Flush

SSE 连接是长连接,必须确保:

  • 连接不被中断
  • 发送后立刻 Flush()
  • 客户端断线后清理资源

4) Hub 模式处理并发

为了避免多个客户端阻塞,学习项目里用了简单的 Hub(广播中心)模型:

type SSEHub struct {
    clients    map[string]*SSEClient
    register   chan *SSEClient
    unregister chan *SSEClient
    broadcast  chan string
}

实践案例(learning-labs/sse)

Go 端点设计

  • /events:基础广播
  • /clock:每秒推送当前时间
  • /stock:模拟股票数据(JSON)

Node 客户端

通过命令行脚本验证不同事件流:

  • basic.js:基础连接与消息接收
  • clock.js:实时时钟订阅
  • stock.js:JSON 解析与渲染

SSE vs WebSocket:我的取舍标准

  • 只需要服务端推送 → 选 SSE
  • 需要双向交互或二进制传输 → 选 WebSocket

SSE 的可维护性与浏览器原生支持,是很多轻量实时场景的优势。


常见坑点(实践中最容易踩)

  1. 代理缓冲:反向代理可能会缓冲响应,导致“推送无效”
  2. 浏览器连接限制:HTTP/1.1 下同域最多 6 条 SSE 连接
  3. Flush 忘记调用:不 Flush 就不会立刻看到消息
  4. 未清理断线:容易导致内存泄漏

快速开始

cd learning-labs/sse/go-example
go run main.go

浏览器访问:http://localhost:8888
或用 Node 客户端测试:

cd learning-labs/sse/node-example
npm install
npm run basic

结语

这次在 @learning-labs 的 SSE 实践更像是一个“最小可运行的知识样本”:
既保留了协议细节,又能通过完整示例把 SSE 的价值感知出来。

如果你也在搭建实时系统,建议把 SSE 当作「轻量级推送」的首选方案之一。