用 net.Dial 建立 TCP 连接需指定 "tcp" 网络类型和 "host:port" 格式地址,推荐用 net.DialContext 配合超时控制;TCP 是字节流,须自行处理粘包/半包、协议边界及安全关闭。

怎么用 net.Dial 建立基础 TCP 连接
直接调用 net.Dial 是最常用、也最容易出错的起点。它返回一个 conn(net.Conn 接口),不是裸 socket,别指望能直接 setsockopt。
常见错误是传错网络类型字符串:写成 "tcp4" 或 "tcp6" 而没考虑目标地址是否真支持;更常见的是把地址写成 "127.0.0.1:8080" 却忘了加端口,或漏了冒号——这时 net.Dial 会报 "address 127.0.0.1: missing port in address"。
- 始终用
"tcp"(自动适配 IPv4/IPv6),除非你明确要限制协议族 - 地址格式必须是
"host:port",host可以是域名(如"example.com:80")或 IP(如"192.168.1.100:3000") - 连接超时需手动控制:用
net.DialTimeout或更推荐的net.DialContext配合context.WithTimeout
示例:
conn, err := net.Dial("tcp", "localhost:9000", nil)
if err != nil {
log.Fatal(err) // 别忽略 err
}
defer conn.Close()
读写数据时为什么卡住或丢包
TCP 是字节流,没有消息边界。你调用一次 conn.Write,对方 Read 不一定刚好收到整段;反过来,一次 Read 也可能只读到半个请求。这不是 bug,是协议特性。
典型现象:客户端发了 "HELLO\n",服务端用 bufio.NewReader(conn).ReadString('\n') 却一直阻塞——因为没收到换行符;或者发了两个包 "A\nB\n",服务端一次 Read 就全读到了,解析逻辑崩了。
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖单次
Write对应单次Read;必须自己定义协议(比如按行、按长度前缀、用 JSON-RPC 等) - 用
bufio.Reader/bufio.Writer提升效率,但注意Write后要Flush,否则可能缓存不发 - 如果服务端不按约定关闭连接,
Read返回io.EOF是正常结束;返回net.OpError(如"use of closed network connection")才是异常
如何安全关闭连接并处理粘包/半包
调用 conn.Close() 只是关闭本地套接字,不保证对方立刻收到 FIN;更关键的是,关闭前必须确保所有待写数据已发出,否则会静默丢弃。
容易被忽略的点:在 goroutine 中异步写,主流程一调 Close,写协程还在往已关闭的 conn 写,触发 panic:"write on closed connection"。
- 写操作务必检查
err,尤其Write和Flush的返回值 - 用
sync.WaitGroup或context控制写协程生命周期,关连前先通知其退出 - 处理粘包:服务端不能假设每次
Read都是完整业务帧;建议在协议层加长度头(如 4 字节 uint32 表示后续内容长度),再循环Read直到收满
示例(带长度头的读):
var length uint32
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
return err
}
buf := make([]byte, length)
_, err := io.ReadFull(conn, buf) // 保证读满
连接复用和长连接要注意什么
HTTP/1.1 默认 keep-alive,但纯 TCP 没这机制。如果你反复 Dial → Write/Read → Close,会频繁创建销毁 socket,触发 TIME_WAIT 堆积,甚至端口耗尽。
真实场景中,比如 MQTT 客户端、Redis 客户端、自研推送服务,都要求长连接保活 + 心跳。但 Go 的 net.Conn 本身不提供心跳,得自己实现。
- 不要为每次请求新建连接;一个
conn复用多次,直到业务明确要断开 - 用
SetDeadline/SetReadDeadline防止读卡死,比如设 30 秒,超时后检查是否还有未读数据,再决定重连还是继续 - 心跳建议用
SetWriteDeadline+ 定期Write一个轻量包(如单字节0x00),服务端响应 ACK;别只靠KeepAlive(OS 层的 TCP keepalive 间隔太长,且不可控)
复杂点在于:心跳失败 ≠ 连接断了(可能是临时丢包),但连续几次失败就得主动关连重建。这个策略边界,得结合你的服务 SLA 来定,没法通用。


















