返回归档
🧶工程与设计

SSE

文章目录

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 的事件流),直到连接关闭。

REF

bookmark

bookmark