Go服务捕获os.Interrupt信号时如何向SSE客户端推送关停通知
实现方案
核心逻辑是在捕获到中断信号后,优先向SSE通道推送关停通知,预留足够时间保证消息发送到客户端后,再执行资源回收和服务退出流程,需要解决三个问题:无客户端连接时通道发送阻塞、消息未发送完成程序就退出、HTTP服务强制断开连接导致消息丢失。
具体修改步骤
- 调整HTTP服务启动逻辑,保留
http.Server实例用于后续优雅关闭 - 捕获到中断信号后,先构造关停通知的SSE消息,用带超时的select向data通道发送消息,避免无活跃客户端时永久阻塞
- 预留短时间缓冲保证SSE处理器把消息flush到客户端
- 执行原有客户端断开等资源回收逻辑
- 优雅关闭HTTP服务后再退出程序
修改后的代码示例
main 函数部分
需要额外导入context和time包支撑优雅关闭逻辑:
package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" ) var passer *DataPasser const maxClients = 1 func init() { passer = &DataPasser{ data: make(chan sseData), logs: make(chan string), connection: make(chan struct{}, maxClients), } } func main() { http.HandleFunc("/sse", passer.HandleSignal) // 保留server实例用于后续优雅关闭 srv := &http.Server{Addr: ":1234"} go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Printf("HTTP service start failed: %v\n", err) } }() // 监听系统中断信号 c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c // 构造服务关停的SSE消息 shutdownMsg := sseData{ event: "server_shutdown", message: "Server is shutting down, connection will be closed.", } // 带超时发送消息,无活跃客户端时直接跳过避免阻塞 select { case passer.data <- shutdownMsg: // 预留500ms等待消息刷入客户端连接 time.Sleep(500 * time.Millisecond) case <-time.After(100 * time.Millisecond): // 无SSE连接接收消息,直接进入退出流程 } // 原有业务资源回收逻辑 if client.IsConnected() { client.Disconnect() } // 优雅关闭HTTP服务,最长等待2秒处理剩余请求 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { fmt.Printf("HTTP service shutdown error: %v\n", err) } }
SSE 处理器部分
原有逻辑已经可以正常接收并发送关停消息,可额外加个判断,发完关停消息后主动释放连接资源:
case data := <-p.data: fmt.Println("received") switch { case len(data.event) > 0: fmt.Fprintf(w, "event: %v\ndata: %v\n\n", data.event, data.message) case len(data.event) == 0: fmt.Fprintf(w, "data: %v\n\n", data.message) } flusher.Flush() // 收到关停事件后主动退出处理循环 if data.event == "server_shutdown" { <-p.connection fmt.Println("Server shutdown, connection closed") return }
注意事项
- 不要直接向无缓冲的
data通道同步发送关停消息:如果当前没有客户端连接SSE,没有接收者的通道发送操作会永久阻塞,导致程序无法退出 - 不要收到信号后立刻退出:Go会在main函数返回时直接终止所有运行中的goroutine,SSE消息还没完成网络发送就会被切断,必须预留足够的缓冲时间
- 后续如果调整
maxClients支持多客户端连接,建议给每个SSE连接维护独立的消息通道,关停时遍历所有活跃连接发送消息,避免单通道被业务消息占用导致关停消息无法及时送达
内容的提问来源于stack exchange,提问作者Hasan A Yousef
相关产品推荐
相关产品推荐

