go tcp连接需显式控制超时、避免并发读写、正确关闭:dial必须用net.dialer设timeout,地址须含端口;read/write非线程安全,需加锁或分离goroutine;每次读写前重设deadline;close应在写完后调用且不可重复。

Go 的 TCP 连接不是“连上就完事”,不显式控制超时、不正确关闭、并发读写同一 conn,90% 的线上连接异常都由此而来。
net.Dial 默认阻塞,DNS 解析失败会卡死
直接用 net.Dial("tcp", "example.com:8080") 在生产环境极其危险:它不区分 DNS 解析耗时和 TCP 连接耗时,一旦域名解析慢或失败,goroutine 就卡在那儿,既不报错也不超时。
- 必须用
&net.Dialer{Timeout: 3 * time.Second, KeepAlive: 30 * time.Second}显式控制——Timeout覆盖 DNS + connect 全过程 - 别用
context.WithTimeout包裹net.Dial:context 只能中断 connect 阶段,DNS 仍可能卡住 - 地址字符串必须含端口,
"localhost:8080"合法,"localhost"或"8080"会直接 panic:listen tcp: address 8080: missing port in address
conn.Read/Write 不是线程安全的,别并发调用
net.Conn 接口本身不保证并发安全。同一个 conn 上同时 Read 和 Write,轻则数据错乱,重则 runtime panic:concurrent read and write on connection。
- 常见错误:一个 goroutine 循环
Read,另一个 goroutine 直接Write响应——必须加sync.Mutex或拆成 reader/writer 专用 goroutine -
conn.SetReadDeadline和conn.SetWriteDeadline是 per-call 的,每次读/写前都得重设,否则超时逻辑失效 - 不设 deadline 时,对端静默断连(如拔网线)会导致
Read永久阻塞,默认 TCP keepalive 触发要等 2 小时
连接关闭必须先写后关,且不能重复 Close
conn.Close() 不是“保险丝”,而是明确的资源释放动作。错误关闭会堆积 TIME_WAIT、耗尽本地端口,甚至让服务端无法感知断连。
- 务必在业务逻辑结束、写完响应后再调用
conn.Close();若先关再写,会立刻返回write: broken pipe - 重复调用
Close()会返回use of closed network connection错误,尤其在 defer 中嵌套或错误重试逻辑里容易踩坑 - 连接关闭后,
conn.Read()立即返回io.EOF,但conn.Write()可能延迟报错,不能靠err == nil判断连接是否可用
长连接场景下,别自己手写连接池
自建 sync.Pool 存 net.Conn 看似简单,实则极易漏掉关键校验:空闲连接可能已断开、TCP keepalive 未启用、deadline 未重置。
- HTTP 场景优先用
http.Transport,它内置连接复用、健康检查和自动重连 - 自定义协议必须池化时,从
sync.Pool获取后,必须调用conn.SetReadDeadline(time.Now().Add(…))做存活探测,再Read一个字节确认可读 - 池中连接使用完毕后,不要
Close(),而是return回池;真正废弃时才调用Close
最常被忽略的点:TCP 连接的“生命周期”不在 Dial 和 Close 之间,而在每一次 Read/Write 调用的上下文中——deadline、错误判断、缓冲区管理,这些细节才是稳住连接的关键。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











