Go中TCP通信需注意:服务器用net.Listen监听并循环Accept+goroutine处理连接;客户端用Dialer设超时避免阻塞;Read需处理部分读取和关闭情况;关闭连接要先写后关,避免broken pipe。
TCP 客户端与服务器在 Go 中实现起来非常直接,但容易在连接管理、读写阻塞和错误处理上出问题——关键不是“能不能连上”,而是“连上后怎么稳住”。
如何用 net.Listen 启动一个基础 TCP 服务器
Go 的 net.Listen 返回一个 net.Listener,它本身不处理连接逻辑,只负责接收新连接。常见误区是以为调用一次就能服务多个客户端,其实必须显式循环 Accept()。
- 监听地址格式必须带端口,例如
"localhost:8080"或":8080";写成"8080"会报错listen tcp: address 8080: missing port in address - 务必用
defer ln.Close(),否则端口可能被占用无法重启 - 每个
conn都要单独 goroutine 处理,否则第二个连接会被第一个阻塞住
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println("Accept error:", err)
continue // 别 panic,继续收下一个连接
}
go handleConnection(conn) // 每个连接一个 goroutine
}
如何用 net.Dial 建立 TCP 客户端并避免阻塞卡死
net.Dial 默认是同步阻塞的:如果目标地址不可达或防火墙拦截,它可能卡住几十秒才超时。生产环境必须控制连接建立耗时。
- 用
net.DialTimeout或更推荐的net.Dialer+Context控制超时 - 不要对
conn.Write或conn.Read不设限地读写大块数据,容易因对方未响应而永久阻塞 - 客户端也要显式
conn.Close(),否则文件描述符泄漏(尤其在压测时快速建连断连)
dialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}
conn, err := dialer.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal(err) // 如:dial tcp [::1]:8080: connect: connection refused
}
defer conn.Close()
为什么 conn.Read 有时只读到部分数据,甚至返回 0
TCP 是字节流协议,Read 不保证一次把应用层“一条消息”读完。它只按内核缓冲区当前可用字节数返回,可能是 1 字节,也可能是 1KB,也可能返回 n == 0, err == nil(对端关闭连接)或 io.EOF。