最直接方式是调用net.Dial("tcp", "host:port")获取net.Conn;必须用小写"tcp",地址含端口,需处理错误、设读写deadline、正确关闭连接。

怎么用 net.Dial 建立 TCP 连接
Go 里最直接的方式就是调用 net.Dial,它返回一个 net.Conn 接口,底层自动处理 DNS 解析、TCP 握手和连接超时。别自己写 socket syscall,也别用 net.DialTimeout(已弃用)。
常见错误是传错网络类型字符串:必须是 "tcp",不是 "TCP" 或 "tcp4"(后者虽能工作但不通用,IPv6 场景会失败)。
-
conn, err := net.Dial("tcp", "127.0.0.1:8080", nil)—— 正确 -
net.Dial("tcp", "localhost:8080", nil)—— 也行,但依赖系统 hosts 和 DNS,调试时不如 IP 明确 - 如果服务器没启动,
err是dial tcp 127.0.0.1:8080: connect: connection refused,不是空指针或 panic
怎么安全地发送和接收字节流
TCP 是字节流协议,没有消息边界。不能假设一次 Write 对应一次 Read,也不能指望 Read 一次性读完所有数据。必须自己处理粘包/拆包。
简单场景(如发一条 JSON 后等响应),可用 bufio.Writer + bufio.Reader 配合 WriteString 和 ReadString('\n');但生产环境更推荐固定头长或长度前缀(比如 4 字节 uint32 表示 body 长度)。
立即学习“go语言免费学习笔记(深入)”;
-
conn.Write([]byte("HELLO"))返回写入字节数,不一定等于 len("HELLO"),要检查返回值和err -
n, err := conn.Read(buf)中n可能远小于len(buf),且err == nil不代表读完了,只代表这次没出错 - 别用
io.ReadAll(conn),服务端不关闭连接时会永久阻塞
怎么控制超时避免卡死
默认情况下,net.Conn 的读写操作是阻塞的,且无超时。一旦网络异常或对方不响应,goroutine 就卡住。必须显式设置超时。
推荐用 SetDeadline(绝对时间)或 SetReadDeadline/SetWriteDeadline(相对时间),而不是靠 context.WithTimeout 包裹整个 I/O 操作——因为 context 取消后连接仍处于半开状态,资源没释放。
-
conn.SetDeadline(time.Now().Add(5 * time.Second))—— 同时影响下一次读和写 -
conn.SetReadDeadline(time.Now().Add(3 * time.Second))—— 更灵活,读超时不影响写 - 超时触发的
err是read: i/o timeout或write: i/o timeout,可据此区分是网络问题还是业务逻辑慢
怎么正确关闭连接并处理 EOF
调用 conn.Close() 是必须的,否则 fd 泄露。但关闭时机很重要:不能在 Read 返回 io.EOF 后还继续读,也不能在写失败后忽略错误强行关。
io.EOF 表示对方已关闭连接(FIN),这是正常结束信号;而其他 err(如超时、断连)往往意味着异常中断,需要重连或报错。
if err == io.EOF { /* 对方优雅关闭,可以退出循环 */ }if err != nil && err != io.EOF { /* 网络异常,记录日志,考虑重试 */ }- 关闭前确保写缓冲区已 flush:对
bufio.Writer要调w.Flush(),否则最后几字节可能丢
真正麻烦的是长连接中“心跳”和“重连”的配合——超时判断、重试退避、连接池复用,这些都不是 net.Conn 本身管的事,得靠上层逻辑兜底。


















