使用 Gin 框架来实现 SSE (Server-Sent Events) 是一个非常常见的需求,因为 Gin 轻量、高效,非常适合构建 API 和实时服务。
SSE 的核心在于服务器需要保持一个长连接,并持续不断地向客户端推送数据。这与 Gin 默认的“请求-响应”模式有所不同,我们需要手动处理响应流的写入和连接的保持。
下面我将为你提供一个从简单到完整的实现方案,并附上详细解释和前端测试代码。
1. 核心概念回顾
在开始之前,我们再次明确 SSE 的几个关键点:
- 响应头: 服务器必须设置以下响应头:
Content-Type: text/event-stream: 告诉浏览器这是一个事件流。Cache-Control: no-cache: 防止代理或浏览器缓存数据。Connection: keep-alive: 保持 TCP 连接。
- 数据格式: 每条消息都必须遵循特定格式,以换行符结尾,并且一条完整的消息以一个空行(
\n\n)结束。data: This is a message\n\n event: userLogin\ndata: {"id": 123}\n\n - 刷新缓冲区: 在写入数据后,必须手动调用
http.ResponseWriter的Flush()方法,将数据立即发送给客户端,而不是等待缓冲区填满。
2. 方案一:基础实现
这个方案将演示最基本的 SSE 设置:一个端点,每秒向客户端发送一个包含时间戳的消息。
服务器端代码 (main.go)
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的 Gin 引擎
r := gin.Default()
// 定义 SSE 端点
r.GET("/sse", func(c *gin.Context) {
// 1. 设置响应头
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
// 允许跨域 (如果前端不在同一个源)
c.Header("Access-Control-Allow-Origin", "*")
// 2. 获取底层的 http.ResponseWriter
// 我们需要它来调用 Flush()
w := c.Writer
// 3. 创建一个通道,用于在客户端断开连接时通知循环结束
notify := w.CloseNotify()
// 4. 循环发送消息
for i := 0; ; i++ {
select {
case <-notify:
// 如果客户端断开连接,退出循环
log.Println("Client closed the connection.")
return
default:
// 5. 格式化并发送消息
// 每条消息必须以 \n\n 结尾
message := fmt.Sprintf("data: Message %d at %s\n\n", i, time.Now().Format(time.RFC3339))
_, err := w.Write([]byte(message))
if err != nil {
log.Printf("Error writing to client: %v", err)
return // 如果写入出错,也退出循环
}
// 6. 刷新缓冲区,将数据立即发送给客户端
w.Flush()
// 等待一秒
time.Sleep(1 * time.Second)
}
}
})
// 启动服务器
log.Println("SSE server started at http://localhost:8080/sse")
if err := r.Run(":8080"); err != nil {
log.Fatal

最低0.47元/天 解锁文章
1893

被折叠的 条评论
为什么被折叠?



