net.listen后直接accept会卡住新连接,因accept阻塞且单goroutine串行处理;须用for循环持续accept,并为每个conn启新goroutine处理,同时设read/write deadline防goroutine泄漏。

为什么 net.Listen 后直接 Accept 会卡住新连接?
因为 Accept 是阻塞调用,单 goroutine 处理一个连接时,下一个 Accept 调用得等前一个连接完全处理完才执行——实际效果是“串行接受”,并发能力归零。
- 必须把
Accept放进for循环,且每次接收到的conn立即丢进新 goroutine 处理 - 别在 goroutine 里重复调用
Accept,那是listener.Accept()的职责 - 忘记
defer conn.Close()或没做超时控制,会导致 fd 泄漏,跑一阵就报too many open files
怎么安全地读写 net.Conn 并避免粘包?
Go 的 net.Conn.Read 不保证一次读完所有数据,也不区分消息边界。TCP 是字节流,没有“自动分包”这回事。
- 不要假设
Read会填满你给的[]byte;它返回实际读到的长度,必须检查n, err := conn.Read(buf) - 简单协议可用固定头长(如前 4 字节存 payload 长度),先读够头再读 body;复杂场景建议换
bufio.Reader+ 自定义分隔符 -
Write也可能只写出部分数据,但标准库io.WriteString和json.Encoder已封装好,优先用它们
net/http.Server 和手写 net.Listener 选哪个?
如果只是传原始字节、做透传代理、或对接嵌入式设备私有协议,手写 net.Listener 更轻量;一旦涉及路由、header、TLS、keep-alive 管理,立刻切到 http.Server。
-
http.Server内置连接池、超时控制(ReadTimeout/WriteTimeout)、HTTP/2 支持,自己实现容易漏掉SetDeadline导致连接 hang 住 - 手写 TCP 服务器时,别用
fmt.Fprintln(conn, ...)做响应——它不带换行符的底层行为不可控,改用conn.Write([]byte("OK\n")) - 调试时用
telnet localhost 8080最直接;看到连接建立但无响应,先lsof -i :8080看 fd 是否堆积
goroutine 泄漏比性能慢更致命
每个连接起一个 goroutine 很方便,但客户端异常断连、读写超时、panic 未 recover,都可能让 goroutine 卡在 Read 或 Write 上永远不退出。
立即学习“go语言免费学习笔记(深入)”;
- 务必对每个连接设置读写 deadline:
conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - 用
context.WithTimeout包裹业务逻辑,超时后主动关闭conn - 别依赖
runtime.NumGoroutine()监控——它只反映当前数量,看不出是否在等待 I/O
真正难排查的是那种“连接数不多,但 goroutine 持续增长”的情况:往往是因为某个错误分支忘了关 conn 或没设 deadline。










