gin不支持单个engine实例监听多端口,因router.run()基于http.listenandserve()会阻塞且不可重入;必须用多个独立http.server各挂载专属gin.engine,并通过errgroup统一管理启停。

Gin 本身不支持单个 *gin.Engine 实例直接监听多个端口——router.Run() 只能绑定一个地址,强行调用多次会 panic 或阻塞。真要实现多端口,必须用多个独立的 HTTP server 实例,各自挂载不同的路由逻辑。
为什么不能直接 router.Run(":8000"), router.Run(":8001")?
因为 router.Run() 内部调用的是 http.ListenAndServe(),该函数会阻塞当前 goroutine,并且一旦启动就接管整个进程的信号处理;第二次调用时,要么端口已被占用(listen tcp :8001: bind: address already in use),要么因前一个未返回而根本执行不到。
常见错误现象:
- 只看到第一个端口生效,第二个无响应
- 程序启动后立即 panic,报错
http: Server closed或address already in use - 用
go router.Run(":8001")启动协程,但没做同步控制,主 goroutine 退出导致服务秒退
用 http.Server + errgroup 启动多个独立 Gin 实例
核心思路:每个端口对应一个 *http.Server,每个 server 挂载自己专属的 *gin.Engine(或复用同一实例但需确保路由无冲突),再用 golang.org/x/sync/errgroup 统一等待所有 server 退出。
实操要点:
- 每个
*gin.Engine必须独立创建(gin.New()或gin.Default()),不能共用同一个实例去.Serve()多次 -
http.Server.Addr必须显式设置,如":8000"、":8001" - 务必为每个 server 设置
Handler字段,值为对应*gin.Engine(它实现了http.Handler) - 用
errgroup.Group启动 goroutine 并捕获各 server 的 error,避免某个崩溃导致整体静默退出
示例关键片段:
var g errgroup.Group
g.Go(func() error {
srv1 := &http.Server{
Addr: ":8000",
Handler: router01(),
}
return srv1.ListenAndServe()
})
g.Go(func() error {
srv2 := &http.Server{
Addr: ":8001",
Handler: router02(),
}
return srv2.ListenAndServe()
})
if err := g.Wait(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
如何让不同端口服务完全隔离路由和中间件?
每个端口应有自己专属的 *gin.Engine 实例,这样中间件、路由组、全局配置互不影响。比如管理后台走 :8001,API 接口走 :8000,两者可分别启用 Logger()、Recovery(),甚至不同 CORS 策略。
注意点:
- 不要把两个
router01()和router02()都注册到同一个gin.Default()实例上再分发——Gin 没提供运行时端口路由分发能力 - 若需共享部分中间件逻辑(如 JWT 解析),可提取成函数,分别在各 engine 中
.Use(),而非跨实例复用中间件实例 - 静态文件路径、模板目录等也需各自设置,
e.LoadHTMLGlob()不会自动跨实例生效
优雅关闭时各端口必须独立 Shutdown
收到中断信号(如 Ctrl+C)时,不能只调 srv1.Shutdown(),否则 srv2 还在跑。必须对每个 *http.Server 显式调用 Shutdown(),并等待全部完成。
典型做法:
- 用
signal.Notify()监听os.Interrupt和syscall.SIGTERM - 启动 shutdown goroutine,在收到信号后遍历所有 server 调用
Shutdown() - 每个
Shutdown()应设超时(如 5s),避免卡住 - 原
errgroup.Wait()会因http.ErrServerClosed返回,需忽略该错误
最容易被忽略的是:没给每个 server 单独设 ReadTimeout/WriteTimeout,导致 Shutdown() 等待时间不可控,尤其在有长连接或流式响应时。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











