You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:24:40