ListenAndServe 一启动就退出是因为误用 HTTP 函数跑 TCP 服务;正确做法是用 net.Listen + for 循环 accept;需处理 TCP 流式读取、goroutine 泄漏、超时控制及连接生命周期管理。

ListenAndServe 为什么一启动就退出?
因为 http.ListenAndServe 是阻塞调用,但你误用了它来跑纯 TCP 服务——它只处理 HTTP 协议,不是通用 socket 监听。真要写 TCP 服务端,得用 net.Listen("tcp", ":8080") 手动监听。
常见错误现象:panic: listen tcp :8080: bind: address already in use,说明端口被占;或程序秒退,其实是没写 accept 循环。
- 必须用
for循环持续调用listener.Accept(),否则只收一个连接就结束 -
net.Listen返回的net.Listener需要显式Close(),否则进程退出时可能残留句柄 - 注意地址格式:
":8080"绑定所有网卡,"127.0.0.1:8080"只限本地,生产环境别漏掉0.0.0.0或具体 IP
conn.Read() 读不到完整消息怎么办?
TCP 是流式协议,conn.Read() 不保证一次读完一条“逻辑消息”,可能只读到半个包、也可能合并多个小包。没有内置消息边界,得自己处理。
典型表现:客户端发了 "HELLO\n",服务端 Read() 却只拿到 "HE",下一次才拿到 "LLO\n";或者发三次短消息,Read() 一次性返回全部字节。
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖单次
Read()的长度,要用循环 + 缓冲区拼接,直到收到完整帧(比如遇到\n或指定长度头) - 如果协议简单,可用
bufio.NewReader(conn).ReadString('\n')自动按行切分,但它会阻塞直到遇到换行符或出错 - 避免无限制拼接:设置最大缓冲区长度(如 1MB),超长直接断连,防内存耗尽
goroutine 泄漏导致连接堆积
每个新连接都启一个 go handleConn(conn) 看似合理,但如果 handleConn 里有阻塞读、没设超时、或 panic 后没 recover,goroutine 就永远卡住,连接不关闭,资源持续累积。
现象:连接数不断上涨,netstat -an | grep :8080 | wc -l 越来越多,runtime.NumGoroutine() 居高不下。
- 必须为每个连接设置读写超时:
conn.SetReadDeadline(time.Now().Add(30 * time.Second)) -
handleConn函数入口加defer recover(),否则 panic 会导致 goroutine 消失但连接未关 - 考虑用
context.WithTimeout控制整个连接生命周期,比单纯设 deadline 更可控
客户端 connect refused 或 timeout 怎么快速定位?
不是代码写错了,大概率是服务端根本没起来、端口不对、防火墙拦了,或客户端连的是 localhost 而服务端绑的是 127.0.0.1(IPv4/IPv6 解析差异)。
错误信息:dial tcp [::1]:8080: connect: connection refused 表示服务未监听;i/o timeout 多是网络层拦截或服务卡死。
- 先在服务端机器上用
netstat -tuln | grep :8080确认监听状态和绑定地址 - 用
telnet 127.0.0.1 8080或nc -zv 127.0.0.1 8080测试端口通不通 - 客户端改用
net.DialTimeout("tcp", "127.0.0.1:8080", 5*time.Second),避免无限等待
TCP 编程里最麻烦的从来不是写几行 Accept 和 Read,而是超时控制、错误恢复、连接生命周期管理这些“边角料”。很多人卡在明明代码能跑通,一压测就崩,问题往往出在没真正理解 conn.SetDeadline 的作用范围,或者把 bufio.Scanner 当万能解码器用——它默认 64KB 缓冲,超长行直接报错,而这个限制很难被注意到。











