net.Dial是Go TCP客户端核心,需指定"tcp"和带端口的地址;须处理粘包/拆包、设超时(DialTimeout或net.Dialer)、Close()后仍可能读到残留数据。

如何用 net.Dial 建立 TCP 连接
Go 的 TCP 客户端核心就是 net.Dial,它返回一个 net.Conn 接口,可直接读写。调用时需指定网络类型("tcp")和地址("host:port"),地址必须带端口号,不能只写域名或 IP。
常见错误是传入 "127.0.0.1" 或 "localhost" 而漏掉端口,导致 panic:dial tcp: address 127.0.0.1: missing port in address。
- 正确写法:
conn, err := net.Dial("tcp", "127.0.0.1:8080") - 支持域名解析:
net.Dial("tcp", "example.com:443"),会自动查 DNS - 若服务未监听,
err是*net.OpError,可通过errors.Is(err, syscall.ECONNREFUSED)判断连接被拒
如何安全地发送和接收数据
TCP 是字节流协议,没有消息边界。Conn.Write() 和 Conn.Read() 不保证一次收发完整业务数据 —— Read() 可能只读到部分包,也可能合并多个小包。
别直接用 Read([]byte) 期待“一次读完一行”或“一次读完一个 JSON”。实际中必须自己处理粘包/拆包。
立即学习“go语言免费学习笔记(深入)”;
- 简单场景可用
bufio.Scanner按行读:scanner := bufio.NewScanner(conn),但仅适用于换行分隔的协议 - 二进制协议建议先读 4 字节长度头(
binary.Read(conn, binary.BigEndian, &length)),再按长度读取正文 -
Write()返回已写字节数,务必检查是否等于预期长度,否则需重试或报错
如何设置超时避免卡死
默认情况下,net.Dial、Read()、Write() 都可能无限阻塞。生产环境必须设超时。
- 连接超时:用
net.DialTimeout("tcp", addr, 5*time.Second) - 更灵活的方式是用
net.Dialer:dialer := &net.Dialer{Timeout: 3 * time.Second, KeepAlive: 30 * time.Second} conn, err := dialer.Dial("tcp", "127.0.0.1:8080") - I/O 超时需在连接建立后单独设置:
conn.SetDeadline(time.Now().Add(10 * time.Second))(影响后续所有读写);或分别设:conn.SetReadDeadline(...)/conn.SetWriteDeadline(...)
为什么 Close() 后还可能收到数据
conn.Close() 只是关闭本端写通道,并触发 FIN 包;对端仍可继续发数据,直到它也关闭。因此 Close() 后立即 Read() 仍可能成功返回残留数据,甚至返回 io.EOF(表示对端已关读)。
- 不要假设
Close()后连接立刻“空了”,尤其在并发读场景下 - 若需强制终止,可先设极短读超时再
Close(),或用SetLinger(0)(需转换为*net.TCPConn) - 注意:多次
Close()是安全的,但不会报错也不会重复生效
真正麻烦的是连接中途断开又没及时检测——得靠心跳、读超时或 conn.RemoteAddr() 是否有效来间接判断,没有银弹。










