net.Dial 是 Go 中建立 TCP 连接最直接方式,但默认无超时且行为受系统影响;需用 net.Dialer 精确控制超时、KeepAlive 等参数,并妥善处理粘包、重连与资源清理。

用 net.Dial 建立基础 TCP 连接
Go 中最直接的 TCP 客户端创建方式就是调用 net.Dial,它封装了底层 socket 创建、连接等逻辑,返回一个 net.Conn 接口实例。默认使用阻塞模式,连接失败会立即返回 error。
常见错误现象:调用后卡住几秒才返回 dial tcp 127.0.0.1:8080: i/o timeout —— 这是系统级连接超时(通常 30 秒),不是 Go 控制的。
- 必须显式指定网络类型,TCP 场景下固定为
"tcp"(IPv4)或"tcp4"/"tcp6" - 地址格式为
"host:port",不带协议头;"localhost:8080"和"127.0.0.1:8080"行为可能不同(涉及 DNS 解析和 dual-stack) - 若需控制超时,不能依赖
net.Dial默认行为,应改用net.DialTimeout或更灵活的net.Dialer
conn, err := net.Dial("tcp", "127.0.0.1:8080", nil)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
, = conn.Write([]byte("HELLO\n"))
buf := make([]byte, 128)
n, _ := conn.Read(buf)
log.Printf("received: %s", buf[:n])
用 net.Dialer 精确控制连接行为
当需要设置超时、绑定本地地址、禁用 KeepAlive、自定义 DNS 解析或复用连接池时,net.Dialer 是必选项。它把连接参数从函数参数中解耦出来,也便于测试 mock。
容易踩的坑:Dialer.Timeout 只控制「建立连接阶段」超时,不影响后续读写;KeepAlive 设为 0 会关闭 OS 层心跳,但某些服务端仍可能因中间设备(如 NAT)断连。
-
Dialer.Timeout:连接建立最大等待时间(推荐设为 3–5 秒) -
Dialer.KeepAlive:TCP keep-alive 探测间隔(如30 * time.Second),设为 0 则禁用 -
Dialer.LocalAddr:指定本地绑定地址(例如多网卡场景下选特定出口 IP) -
Dialer.Resolver:可替换默认 DNS 解析器,用于测试或定制解析逻辑
dialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}
conn, err := dialer.Dial("tcp", "example.com:443")
if err != nil {
log.Fatal(err)
}处理连接异常与重连逻辑
TCP 连接在生产环境大概率会断开:服务端重启、网络抖动、防火墙超时、对方主动 close。Go 的 net.Conn 一旦关闭或出错,不可恢复,必须新建连接。
关键点:不要在 Read 或 Write 出错后继续使用该 conn;也不要对已关闭连接调用 Close()(会 panic);重连前务必检查 error 是否属于临时性错误(net.ErrClosed、io.EOF、syscall.ECONNREFUSED 等)。
- 用
errors.Is(err, io.EOF)或errors.Is(err, net.ErrClosed)判断是否可重试 - 区分临时错误(
net.OpError+Temporary() == true)和永久错误(如 DNS 解析失败) - 避免无限重连:加退避(backoff),比如指数增长延迟,或限制最大重试次数
发送/接收数据时注意粘包与缓冲区管理
TCP 是字节流协议,conn.Write() 和 conn.Read() 不保证一次调用对应一“条”业务消息。客户端发 3 次 Write,服务端一次 Read 可能拿到全部数据;也可能一次 Write 被拆成多次 Read 返回 —— 这就是粘包/半包问题。
除非协议本身是「行分隔」(如 HTTP/1.1 的 \r\n)或「定长包」,否则必须自行处理消息边界。常见做法是加长度头(4 字节 big-endian 表示 payload 长度)或特殊分隔符。
- 不要假设
Read会填满传入的[]byte缓冲区;总是检查返回的n值 - 写入大块数据时,
Write可能只写部分,需循环调用或使用io.WriteString/bufio.Writer - 高吞吐场景建议用
bufio.Reader+ReadString('\n')或ReadBytes('\n')处理行协议
真正难的不是连上,而是连上之后怎么稳住、怎么识别消息、怎么安全清理资源 —— 这些细节不写进日志,就只能靠连接断了再看报错。










