http.server 没有 maxconns 字段,因标准库仅提供 maxidleconns 等空闲连接复用参数,不控制新建连接;真正限流需在 accept() 后用 sync.semaphore 插入信号量,立即 close 拒绝连接并确保 defer release。

为什么 http.Server 没有 MaxConns 字段
标准库 http.Server 确实没有 MaxConns 字段——这不是你漏看了文档,而是它根本不存在。常见误解源于把连接池参数当成连接数限制:MaxIdleConns 和 MaxIdleConnsPerHost 只控制空闲连接复用数量,对新建 TCP 连接完全不设防。哪怕你设成 0,ln.Accept() 依然照常返回新连接,FD 资源持续增长。
sem.TryAcquire(1) 必须在 Accept() 后立刻判断
连接已建立才做限流,是唯一能真实控制 FD 数量的时机。若在 goroutine 启动前用信号量排队,只是延缓任务派发,连接早已被 accept 下来并占用句柄。
-
sem.TryAcquire(1)返回false时,必须立即调用conn.Close(),不能只跳过 handler - 绝不能用
sem.Acquire(ctx, 1)阻塞等待——此时连接已建好,阻塞等于白占一个 FD,极易触发ulimit -n限制 - 释放必须在 goroutine 内部用
defer sem.Release(1),且放在函数最开头,覆盖 panic 路径
用 sync.Semaphore 还是 chan struct{}
Go 1.21+ 推荐直接用标准库 sync.Semaphore,尤其涉及文件、HTTP 客户端或需要上下文取消的场景。手写通道信号量容易漏释放:一旦某个 goroutine 在 os.Open 或 io.Copy 时 panic 且没 defer 释放,后续所有请求永久阻塞。
-
semaphore.NewWeighted(int64(maxConns))初始化时必须传int64,传int会编译失败 -
sem.Acquire(ctx, 1)必须检查 error,context.DeadlineExceeded表示超时,不可忽略 -
len(sem)不可用来判断是否可进——它返回已占用数,不是可用数;且非原子操作,竞态下值瞬间失效
http.Transport 参数和连接数上限是两回事
别指望调大 MaxConnsPerHost 来限制并发连接数。它只管复用连接池里最多保留几个空闲 TCP 连接,和你每秒 accept 多少新连接毫无关系。设成 5 却开了 50 个 goroutine 去 handle,结果就是 50 个活跃连接同时存在,MaxConnsPerHost 完全不起作用。
真正要控的是 accept 层:信号量必须插在 ln.Accept() 和 handleConn() 之间,且 release 位置必须紧贴业务逻辑结束点——比如文件上传要放在 io.Copy 完成后,而不是 goroutine 启动后。











