Go中net.Dial建立TCP连接需严格使用"net.Dial("tcp", "host:port")"格式,协议名错或地址缺冒号会直接报错而非连接失败;默认30秒超时不可控,必须用net.DialContext配合context.WithTimeout设置可取消的连接超时,并在读写前调用SetRead/WriteDeadline确保通信健壮。

Go 用 net.Dial 连 TCP,不设超时大概率卡死,协议名写错或地址格式不对会直接报错,不是连不上——是根本没发起连接。
net.Dial 的参数和常见错误
最简调用是 net.Dial("tcp", "127.0.0.1:3000"),但这两个参数非常敏感:
- 第一个参数必须是
"tcp",不是"tcp4"或"tcp6"(除非你明确要限制 IP 版本);写错会返回dial tcp4: lookup tcp4: no such host这类误导性错误 - 第二个参数必须是
"host:port"格式,"127.0.0.1:3000"可以,"localhost:3000"也行(依赖 DNS),但"127.0.0.13000"(漏冒号)会报address 127.0.0.13000: missing port in address - 如果目标端口没服务监听,错误是
dial tcp 127.0.0.1:3000: connect: connection refused;如果是防火墙拦截或路由不通,才可能超时
必须加连接超时,别信默认行为
net.Dial 默认有约 30 秒系统级超时,但不可控、不一致,线上绝对不能依赖。正确做法是用 net.DialContext:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
conn, err := net.DialContext(ctx, "tcp", "127.0.0.1:3000")
if err != nil {
// err 可能是 *net.OpError,可调用 err.(*net.OpError).Timeout() 判断
log.Printf("connect failed: %v", err)
return
}
- 别用
net.DialTimeout—— 它已弃用,且不支持取消语义 - 超时后
err类型是*net.OpError,不是普通error字符串,需类型断言才能细判 - 即使
DialContext成功,也不代表连接“健康”:后续第一次Write仍可能触发write: broken pipe
连接建立后,读写前必须设 deadline
TCP 连接成功 ≠ 通信稳定。不设读写截止时间,Read 可能永远阻塞(比如服务端发一半就断电),Write 也可能卡在内核发送缓冲区。
立即学习“go语言免费学习笔记(深入)”;
- 用
conn.SetReadDeadline和conn.SetWriteDeadline,传入绝对时间:time.Now().Add(5 * time.Second) - 每次读/写操作前都得重设 deadline,否则第二次调用直接返回
read: i/o timeout - 不要混用
SetDeadline(读写共用)和分设的SetRead/WriteDeadline,逻辑容易错乱 - 错误类型是
os.SyscallError或*net.OpError,不是context.DeadlineExceeded
为什么 Read 会只读到一半,以及怎么应对
TCP 是字节流,conn.Read(buf) 返回的 n 可能远小于 len(buf),甚至为 0(对端关闭)。这不是 bug,是协议本质。
- 永远检查
n, err := conn.Read(buf)中的n,别假设填满 buffer - 遇到
err == io.EOF是正常结束;err != nil && err != io.EOF才是异常中断 - 想按行读,用
bufio.NewReader(conn).ReadString('\n'),但注意它会把\r\n一起读入,需strings.TrimSuffix - 想读固定长度消息(如前 4 字节是 body 长度),先用
io.ReadFull读头,再按长度读 body,避免手动循环
真正难的不是连上,而是连上之后怎么判断“这一包数据到底齐了没有”——这必须由协议层定义,标准库不替你做。


















