go标准库没有listenandservetcp函数,正确做法是用net.listen("tcp", ":8080")获取listener,再通过accept循环处理连接,并为每个conn设置readdeadline超时,配合defer conn.close()和recover防止goroutine泄露。

listenAndServeTCP 不存在,别搜这个函数名
Go 标准库没有 listenAndServeTCP 这个函数,这是新手常踩的第一个坑——被某些过时博客或拼写错误的示例带偏了。实际用的是 net.Listen 配合手动 Accept 循环,或者更常见的 http.Serve(但那是 HTTP 协议层)。纯 TCP 服务端必须自己处理连接生命周期。
正确起点是:net.Listen("tcp", ":8080"),返回一个 net.Listener;之后用 listener.Accept() 阻塞等待连接。
- 端口小于 1024 需要 root 权限(Linux/macOS),开发时优先用
:8080或更高 -
"tcp4"和"tcp6"可指定仅 IPv4/IPv6,不写"tcp"默认双栈,但某些容器环境可能绑定失败,建议明确 - 忘记调用
listener.Close()会导致端口无法释放,调试时反复运行报address already in use
conn.Read 会阻塞,不加超时容易卡死
TCP 连接建立后,conn.Read() 默认无限期等待数据到来。客户端异常断开、网络中断、或只是发了一半包,服务端就会一直挂在那里,goroutine 泄露。
必须为每个 net.Conn 设置读写超时:
- 用
conn.SetReadDeadline(time.Now().Add(30 * time.Second)),不是SetDeadline(它同时影响读写,容易误伤) - 超时后
Read()返回io.EOF或net.ErrClosed,需检查错误类型再决定是否关闭连接 - 不要在循环外只设一次超时——每次
Read()前都得重设,否则第一次超时后后续读永远失败
示例关键片段:
for {<br> conn.SetReadDeadline(time.Now().Add(30 * time.Second))<br> n, err := conn.Read(buf)<br> if err != nil {<br> if netErr, ok := err.(net.Error); ok && netErr.Timeout() {<br> continue // 超时,继续等下一次<br> }<br> break // 其他错误,退出循环<br> }<br> // 处理 buf[:n]<br>}
goroutine 泄露比内存泄露更隐蔽
每 accept 一个连接就起一个 goroutine 处理,很自然。但如果连接没正确关闭、或 handler 里有死循环、或忘了 recover panic,这个 goroutine 就永远卡住,进程内存未必涨,但 runtime.NumGoroutine() 会持续上涨,最终耗尽调度器资源。
- 务必用
defer conn.Close()放在 handler 开头,确保连接终将释放 - 在 handler 最外层加
defer func() { if r := recover(); r != nil { log.Printf("panic: %v", r) } }() - 生产环境建议限制最大并发连接数,比如用带缓冲的 channel 控制 goroutine 数量,而不是无节制
go handle(conn)
测试时 telnet 连不上?先看 listen 地址和防火墙
本地写完 net.Listen("tcp", ":8080"),用 telnet localhost 8080 连不上,90% 是以下三个原因:
- 监听地址是
":8080",但本机防火墙(如 macOS 的“防火墙选项”或 Windows Defender 高级设置)拦截了入站连接 - 代码跑在 Docker 容器里,没加
-p 8080:8080端口映射,或映射了但容器内监听的是127.0.0.1:8080(只允许本地访问) - 用
netstat -an | grep 8080(macOS/Linux)或netstat -ano | findstr :8080(Windows)确认端口确实在 LISTEN 状态,且是*:8080而非127.0.0.1:8080
最简验证方式:同一台机器上,用 nc -v localhost 8080 替代 telnet,它会显示更具体的拒绝原因,比如 Connection refused(服务没起来)还是 Connection timed out(被拦截)。
真正难调的永远不是怎么写通,而是连接建立后数据收发的边界条件——粘包、半包、EOF 判断、超时重试时机。这些不会报错,只会让业务逻辑静默出错。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











