应使用 net.DialTimeout 设置超时避免阻塞,IPv6 地址需加方括号,TCP 无消息边界需自行处理粘包,关闭连接前须确保读写完成并设 deadline,HTTP 场景优先用 http.Client。

怎么用 net.Dial 建立基础 TCP 连接
直接调用 net.Dial 是最常用、也最容易出错的起点。它返回一个 net.Conn,但不会自动处理超时、重连或粘包。
- 必须显式设置连接超时:用
net.DialTimeout,而不是裸调net.Dial,否则 DNS 解析卡住或服务未响应时会无限阻塞 -
net.Dial("tcp", "127.0.0.1:8080", nil)中地址字符串不能省略端口,"127.0.0.1"会报dial tcp: missing port in address - IPv6 地址要加方括号:
"[::1]:8080",否则解析失败
读写数据时为什么常遇到 io.EOF 或卡死
TCP 是字节流,没有消息边界。客户端发一次 Write,服务端可能分多次 Read 到;反过来,一次 Read 也可能只读到半条消息。
-
conn.Read返回io.EOF表示对方已关闭连接,不是错误,但容易被误判为异常退出 - 不要依赖
Read一次性读完全部数据——它只按缓冲区大小和当前可用字节返回,需循环读或配合bufio.Reader - 如果服务端用
WriteString发送文本,客户端用ReadString('\n')更安全;若协议无分隔符,就得自己定义长度头或用encoding/binary解包
如何安全关闭连接并避免 use of closed network connection
连接关闭后继续 Write 或 Read 就会触发这个 panic,但 Go 不会在 close 后自动拦截后续操作。
- 务必在
defer conn.Close()前确保所有读写逻辑结束,尤其别在 goroutine 里异步读的同时主流程就关连接 - 多个 goroutine 并发读写同一
conn时,需自行加锁或用sync.Once控制关闭时机 - 关闭前可先调用
conn.SetDeadline(time.Now().Add(100 * time.Millisecond))避免Write在关闭瞬间阻塞
要不要用 net/http 代替手写 TCP 客户端
如果你实际是在跟 HTTP API 通信,硬写 TCP 层纯属重复造轮子,且大概率漏掉 TLS、重定向、连接复用等细节。
立即学习“go语言免费学习笔记(深入)”;
- 即使服务端是自研 TCP 协议,只要它基于 HTTP(比如 REST over HTTP),就该用
http.Client,不是net.Conn -
http.DefaultClient默认启用连接池,而手写 TCP 每次都新建连接,性能差、资源浪费 - 只有协议明确非 HTTP(如 Redis RESP、MQTT、自定义二进制协议)才需要
net.Conn,此时注意:别把http.Transport的配置套过来,它不生效
真正难的不是连上,而是连上之后怎么判断“消息收完了”、怎么应对服务端突然断连、怎么在并发场景下让读写不打架——这些没标准答案,得看协议设计本身是否健壮。


















