net.listen需配合for循环accept和goroutine处理,否则仅响应单连接;端口冲突、地址格式错误、未设超时、未处理粘包、缺少优雅关闭等是常见问题。

net.Listen 必须配合循环 Accept 和协程处理,否则服务端只能响应一个连接就卡死。
监听失败:端口被占或地址绑定出错
最常见的错误不是代码写错,而是 net.Listen("tcp", ":8080") 直接 panic 报 listen tcp :8080: bind: address already in use。这不是 Go 的问题,是系统层面端口冲突。
- 用
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows)查占用进程,kill 掉再试 - 开发时别硬写
:80、:443或:8080,优先选高位端口如:9000、:9999 - 地址字符串别漏冒号,
"127.0.0.18080"会报missing port in address;写成"127.0.0.1:8080"或更稳妥的":8080"(自动监听所有本地 IPv4/IPv6) - 加一行日志:
log.Printf("listening on %s", addr),确认实际绑定的是哪个地址和端口
只接受一次连接:没启协程导致主线程阻塞
listener.Accept() 是阻塞调用,但真正让服务“看起来只连得上一次”的原因是:你把它放在 for 循环外,或者在循环里同步处理,没丢进 goroutine。
- 必须写成
for { conn, err := listener.Accept(); if err != nil { continue }; go handleConn(conn) } -
handleConn函数参数必须显式接收conn,别用闭包捕获循环变量(比如for _, c := range conns { go func() { ... c.Read() ... }() }会全读最后一个) - 每个
conn都要设读超时:conn.SetReadDeadline(time.Now().Add(30 * time.Second)),防客户端发一半断连导致协程永久挂起 - 记得
defer conn.Close(),否则文件描述符耗尽后,Accept开始返回too many open files
读不到完整消息:TCP 是字节流,不是数据包
调用 conn.Read(buf) 可能只读到半个请求,也可能一次读到两个请求拼在一起——这叫粘包/半包,不是 bug,是 TCP 协议本身特性。
- 如果协议是“前 4 字节表示 body 长度”,用
io.ReadFull(conn, header[:4])先读头,再按长度读 body - 如果是行分隔(如简单文本协议),用
bufio.NewReader(conn).ReadString('\n'),但记得strings.TrimSpace去掉\r\n - 绝对不要只靠
n > 0判断读完,每次都要检查err == io.EOF(正常断连)或err != nil(异常) - 避免裸写
conn.Write([]byte)后不Flush()——bufio.Writer默认缓冲,不 flush 可能一直不发出去
生产环境上线前必须补上的三件事
本地跑通 ≠ 能扛住真实网络。忽略这些,服务上线后大概率出现连接堆积、超时无响应、fd 耗尽等问题。
- 加连接数限制:用
semaphore或原子计数器控制并发连接总数,防突发流量打垮机器 - 启用 KeepAlive:
conn.SetKeepAlive(true)和conn.SetKeepAlivePeriod(30 * time.Second),及时发现死链 - 支持优雅关闭:
server.Shutdown(ctx)(需封装net.Listener)或手动遍历活跃连接调conn.Close(),别直接os.Exit
真正难的不是写通第一版,而是让 handleConn 在各种网络抖动、客户端异常断连、发送中断、超长消息下都不 panic、不泄漏 goroutine、不卡死 fd。这些细节不会在编译时报错,但会在凌晨三点让你收到告警。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











