SSE介绍
SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 的协议,允许服务器主动向浏览器推送数据。它常用于实时更新页面数据,比如消息推送、实时行情、通知等场景。
SSE 是一种 单向通信协议:
- 服务器 → 客户端,只能由服务器发送数据到客户端;
- 使用的是 HTTP 协议(通常是长连接);
- 由浏览器通过
EventSource对象建立连接。
SSE 连接建立到内容发送的完整流程
1. 连接建立阶段
- 客户端发起请求:客户端使用 new EventSource(url) 创建实例,这会自动发送一个 HTTP GET 请求到服务器。请求头中包含 Accept: text/event-stream,表示期望 SSE 格式响应。
- 服务器响应:服务器收到请求后,不立即关闭连接,而是返回 HTTP 200 OK 响应,并设置关键响应头:
- Content-Type: text/event-stream:标识响应为 SSE 事件流。
- Cache-Control: no-cache:禁用缓存,确保实时性。
- Connection: keep-alive:保持长连接。
- 连接成功:一旦响应头发送,连接就建立完成。客户端会触发 onopen 事件,此时通道已就绪,服务器可以开始推送数据。如果连接失败(如网络问题),客户端会触发 onerror 并自动重连(默认每 3 秒一次,可通过 retry 字段自定义)。
这一阶段本质上是 HTTP 握手,但不关闭连接,转为“挂起”状态,支持持续写入。
2. 内容发送阶段
- 服务器推送数据:连接建立后,服务器通过响应流(Response Writer)写入数据。数据必须遵循 SSE 规范:
- 每条消息以 data: 开头,后跟内容(可多行)。
- 可选字段:event: (自定义事件名)、id: (消息 ID,用于重连补发)、retry: (重连间隔,单位 ms)。
- 以空行 \n\n 结束一条消息。
- 客户端接收:客户端通过 onmessage 事件(默认)或 addEventListener(自定义事件)监听。收到数据后,浏览器自动解析 data 字段,并触发相应回调。
- 心跳与维护:为防止连接超时,服务器应定期发送心跳(如 :keep-alive\n\n)。如果客户端断开,服务器需监听请求上下文取消信号,清理资源。
整个发送是单向的:服务器主动写,客户端被动读。数据通常是文本或 JSON 序列化,支持流式输出(如大模型逐字生成)。
sequenceDiagram
participant Client as 客户端 (EventSource)
participant Server as 服务端 (HTTP Handler)
Client->>Server: 发送 GET 请求 (Accept: text/event-stream)
Server->>Client: 返回 200 OK + 响应头 (Content-Type: text/event-stream, keep-alive)
Note over Client,Server: 长连接建立,onopen 触发
loop 内容发送循环
Server->>Client: 写入数据 (data: {内容}\n\n)
Client->>Client: 触发 onmessage,解析 data
end
alt 心跳维护
Server->>Client: 发送 :keep-alive\n\n (每 15-30s)
end
alt 连接关闭
Client->>Server: 关闭 EventSource.close()
Note over Server: 检测上下文 Done,清理资源
end
简单 Go Demo
以下是一个最简的 Go SSE 示例服务器,使用 net/http 包实现。客户端可通过浏览器控制台测试 new EventSource(“http://localhost:8080/events")。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/events", sseHandler)
log.Println("SSE server started on :8080/events")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func sseHandler(w http.ResponseWriter, r *http.Request) {
// 设置 SSE 响应头
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Access-Control-Allow-Origin", "*") // 可选,允许跨域
// 获取 Flusher 支持流式写入
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
// 监听客户端断开
ctx := r.Context()
// 模拟发送 5 条消息
for i := 1; i <= 5; i++ {
select {
case <-ctx.Done():
log.Println("Client disconnected.")
return
default:
// 格式化 SSE 数据
fmt.Fprintf(w, "data: 消息 %d: 当前时间 %s\n\n", i, time.Now().Format(time.RFC3339))
flusher.Flush() // 立即发送
time.Sleep(1 * time.Second) // 模拟延迟
}
}
log.Println("All messages sent.")
}
运行后,访问 /events 路径,服务器会每秒推送一条消息,共 5 条。客户端可在控制台看到输出。
从连接视角看SSE
SSE 的本质是HTTP 长连接,它和普通 HTTP 请求的核心区别在于:普通请求是 “请求 - 响应 - 关闭连接”,而 SSE 请求是 “请求 - 持续响应 - 保持连接”,整个过程从始至终都是同一个连接:
- TCP 层:客户端发起 SSE 请求时,会和服务端建立一次 TCP 三次握手,生成一个唯一的 TCP 连接(由源 IP / 端口、目标 IP / 端口标识);整个 SSE 通信过程中,这个 TCP 连接不会断开,直到主动关闭(客户端
close()、服务端res.end()或网络异常)。 - HTTP 层:SSE 的第一个 GET 请求就是基于这个 TCP 连接发送的;服务端返回的 SSE 响应不是 “一次性返回完”,而是通过这个连接分批次、持续地向客户端发送数据块(也就是 SSE 的事件流),直到连接关闭。