Go标准库http.ListenAndServe不能用于纯TCP服务器,需用net.Listen+Accept循环;每个conn需独立goroutine处理,配合bufio、超时控制、recover及协议解析。

listenAndServe 不能直接用在 TCP 场景
Go 标准库的 http.ListenAndServe 是 HTTP 专用的,它内部绑定了 HTTP 协议解析逻辑。想写纯 TCP 服务器,必须绕过 http 包,直接用 net.Listen + Accept 循环。
常见错误是照着 HTTP 示例改端口,结果连上去立刻断开,或者收不到数据——因为底层没做读写循环,也没处理连接生命周期。
-
net.Listen("tcp", ":8080")返回net.Listener,不是http.Server - 每个
Accept()返回的net.Conn需要单独 goroutine 处理,否则阻塞后续连接 - 别忘了
defer conn.Close(),但要在读写完成后关,不是一进来就关
conn.Read 和 conn.Write 容易卡死或丢数据
TCP 是流式协议,conn.Read 不保证一次读完所有发送内容,conn.Write 也不保证一次发完。直接拿 []byte 往里读写,大概率出问题。
典型现象:客户端发了 "hello\nworld\n",服务端只读到 "hel" 就停了;或者发了 1KB 数据,Write 返回写入 200 字节,剩下 800 字节没处理。
立即学习“go语言免费学习笔记(深入)”;
- 用
bufio.NewReader(conn)+ReadString('\n')处理行协议 - 用
io.Copy或循环调用Write确保全量写出(检查返回值和err) - 不要假设
Read会填满传入的[]byte,永远检查返回的n和err
不设超时会导致连接堆积、goroutine 泄漏
一个没设超时的 TCP 服务跑几天后,可能发现 goroutine 数飙升到几千,netstat 显示大量 ESTABLISHED 连接,但实际没人用——这是客户端异常断开、服务端没感知导致的。
Go 的 net.Conn 支持 SetDeadline、SetReadDeadline、SetWriteDeadline,必须显式设置。
- 在
Accept后立刻对conn调用SetReadDeadline(比如 30 秒) - 每次
Read前重新设置,避免长连接空闲超时 - 不要只设一次:deadline 是绝对时间点,不是相对时长
生产环境必须处理 panic 和连接异常中断
客户端突然拔网线、kill 进程,服务端 Read 会返回 io.EOF 或网络错误;如果代码里有 json.Unmarshal 或其他可能 panic 的操作,没 recover 就会让整个 goroutine 退出,连接资源不释放。
这不是“加个 defer”就能解决的——recover 必须在处理连接的 goroutine 内部,且要确保 conn.Close() 在 recover 后仍执行。
- 每个连接 goroutine 外层包一层
defer func() { if r := recover(); r != nil { log.Println(r) } }() -
Read返回io.EOF或net.ErrClosed属于正常退出,不用打 error 日志 - 遇到
read: connection reset by peer这类错误,直接 break 退出读循环即可
真正麻烦的是粘包边界判断和协议设计本身——标准库不帮你做,得自己定分隔符、长度头或 TLV。这点容易被初学者忽略,等压测时才发现数据错乱。


















