端口被占用是监听失败的最常见原因,需用lsof或netstat查进程并改用高位端口;Accept后须启协程处理连接并设读超时;固定头长协议用io.ReadFull,行分隔用bufio.ReadString;生产环境需加Shutdown、KeepAlive和连接数限制。

监听地址绑定失败:listen tcp :8080: bind: address already in use
端口被占是写第一个 net.Listen 就卡住的最常见原因。不是代码写错了,而是本地已有进程占了那个端口——比如上次没关干净的 server、其他调试中的服务,甚至 Chrome 的某些代理插件。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows)查谁在用 - 开发时优先选高位端口,比如
:8081、:9000,避开:80、:443这类系统敏感端口 - 别硬写死端口,改用命令行参数或环境变量:
port := os.Getenv("PORT"),默认 fallback 到"8080" - 监听前加日志:
log.Printf("starting server on %s", addr),方便确认实际绑定的是哪个地址
连接处理阻塞主线程:为什么只接受一个请求就卡住?
Go 的 net.Listener.Accept 是阻塞调用,但真正卡住的原因往往是:你没对每个 net.Conn 启协程处理,而是直接在 for 循环里同步读写。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每次
accept到连接后,立刻用go handleConn(conn)启动协程,别等它结束 - 注意协程里的
conn必须是函数参数传入,别直接闭包引用循环变量(常见坑:for _, conn := range ...会出错) - 加上超时控制:
conn.SetReadDeadline(time.Now().Add(30 * time.Second)),防客户端发一半就挂机 - 记得 defer
conn.Close(),否则 fd 耗尽后accept直接返回too many open files
io.ReadFull 和 bufio.Reader.ReadString 怎么选?
读 TCP 流时,底层没有“消息边界”,你得自己定协议。选错读法会导致阻塞、截断、粘包,而且错误表现不明显。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 如果协议是固定长度头 + 变长体(比如前 4 字节表示 body 长度),用
io.ReadFull读头,再按长度读 body - 如果是行分隔(如 HTTP/1.0 的简单文本协议),用
bufio.NewReader(conn).ReadString('\n'),但记得 trim\r\n - 避免直接用
conn.Read([]byte)读不定长数据——它可能只读到部分,下次再读又得重试逻辑 - 所有读操作都要检查
err == io.EOF或err != nil,别只看n > 0
生产环境必须加的三件事
本地跑通不等于能上线。TCP server 在真实网络里会遇到连接闪断、慢客户端、突发流量,这些在本地 loopback 下完全感知不到。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
http.Server包裹 TCP logic:即使不用 HTTP 协议,也能复用它的Shutdown、超时、连接池管理能力 - 设置
KeepAlive:ln.(*net.TCPListener).SetKeepAlive(true),防 NAT 超时断连 - 加连接数限制:用
semaphore或简单计数器,避免突发连接打垮内存;别等ulimit报错才想起来
最常被跳过的其实是连接生命周期管理:从 Accept 到 Close 之间,任何一步 panic 都会让 goroutine 泄漏,而这种泄漏在压测前根本看不出来。


















