gin 不控制客户端连接数,因其仅负责 http 路由,tcp 连接由 net.listener 和 http.server 管理;限制每 ip 并发需用 sync.map + realip(从 x-forwarded-for 提取并设可信代理),全局连接限流须用 netutil.limitlistener 在 listen 后、serve 前包装 listener。

为什么 Gin 本身不控制客户端连接数
Gin 是 HTTP 路由层框架,它不处理 TCP 连接建立、监听或关闭——这些由底层 net.Listener 和 http.Server 负责。Gin 中间件拿到的已经是已完成握手的 *http.Request,此时连接早已存在。所以你没法在 Gin 里“拒绝连接”,只能拒绝请求(HTTP 层)。
限制每个 IP 的并发请求数:用 sync.Map + realIP
常见错误是直接用 r.RemoteAddr 当 IP,结果所有请求都记到反向代理(如 Nginx、Cloudflare)的地址上;或者没清理零值项,sync.Map 内存持续增长。
- 真实 IP 必须从
X-Forwarded-For提取,但要先调用r.SetTrustedProxies([]string{"192.168.0.0/16"}),否则可被伪造 - IPv6 地址带方括号(如
[::1]:34567),提取 IP 时得用net.ParseIP()后再.To4()或标准化字符串 -
sync.Map不支持原子增减,必须用atomic.AddInt32配合LoadOrStore模拟计数器 - 每次进入 handler 要 +1,退出时
defer-1;若减到 0,立刻ips.Delete(ip),否则内存泄漏
限制全局并发连接数:用 netutil.LimitListener
golang.org/x/net/netutil.LimitListener 是唯一能真正掐住 TCP 连接入口的方式,但它和 “每 IP 并发数” 完全不是一回事。
- 它作用于
net.Listener层,对所有来源统一计数,不管 IP、路径、协议 - HTTP/2 下一个连接承载多个请求,
LimitListener限的是连接数,不是请求数 - 别在
http.Server.Serve()启动后才加限流——必须在net.Listen()之后、http.Server.Serve()之前包装 listener - 示例:
ln, _ := net.Listen("tcp", ":8080"); ln = netutil.LimitListener(ln, 200),然后传给server.Serve(ln)
别踩的坑:MaxConns 是个不存在的字段
http.Server 根本没有 MaxConns 字段,这是高频误解。它只有 MaxIdleConns 和 MaxIdleConnsPerHost,这两个只影响空闲连接复用,对新建连接完全无效。
- 想控新建连接总数?只能靠
netutil.LimitListener或自定义net.Listener - 想控数据库连接数?那是
sql.DB.SetMaxOpenConns()的事,和 HTTP 连接无关 - 混淆这两层会导致你调了一堆
Server参数却毫无效果
真正难的不是写代码,而是分清「TCP 连接」「HTTP 请求」「数据库连接」这三层资源各自在哪被创建、被计数、被释放。漏掉任何一层,压测时都会突然崩掉。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











