go net包开箱即用但需手动处理超时、并发、错误和连接生命周期;tcp服务需goroutine并发处理conn并显式设deadline;dial应配超时避免卡顿;udp无连接,listenudp可收发任意地址,dialudp绑定远端;安全关闭需waitgroup等待活跃连接结束。

Go 的 net 包开箱即用,适合快速构建 TCP/UDP 服务和客户端,但直接调用底层接口容易忽略连接生命周期、超时控制和错误处理细节。
如何用 net.Listen 启动一个基础 TCP 服务
net.Listen 返回 net.Listener,它本身不处理连接逻辑,只负责接受新连接。常见错误是忘记用 defer ln.Close() 或在循环中未对每个 conn 启动 goroutine 导致阻塞。
- 监听地址格式必须带端口,如
"localhost:8080"或":8080";写成"8080"会报"listen tcp: address 8080: missing port in address" - 建议显式设置读写超时:在
conn上调用SetReadDeadline和SetWriteDeadline,否则空闲连接可能长期滞留 - 典型结构是
for { conn, err := ln.Accept(); go handle(conn) },不加go就变成串行处理,无法并发
为什么 net.Dial 连接失败后常卡住几秒
默认情况下 net.Dial 没有超时,DNS 解析失败或目标端口无响应时会等系统默认 timeout(Linux 通常 30 秒)。这不是 bug,而是设计上把超时控制权交给使用者。
- 改用
net.DialTimeout,例如net.DialTimeout("tcp", "example.com:80", 5*time.Second) - 更推荐用
net.Dialer配合Context:构造&net.Dialer{Timeout: 3*time.Second, KeepAlive: 30*time.Second},再调用dialer.DialContext(ctx, "tcp", addr) - 注意:即使设置了超时,如果 DNS 解析慢(比如 /etc/resolv.conf 配了多个 slow nameserver),仍可能卡在解析阶段 —— 此时需配合
net.Resolver自定义超时
UDP 编程中 net.ListenUDP 和 net.DialUDP 的关键区别
TCP 是面向连接的,UDP 是无连接的,所以 net.ListenUDP 返回的 *net.UDPConn 可同时收发;而 net.DialUDP 创建的连接“绑定”了远端地址,WriteToUDP 不能用,只能用 Write。
立即学习“go语言免费学习笔记(深入)”;
-
ListenUDP常用于服务器:用ReadFromUDP接收任意来源数据,用WriteToUDP回复指定地址 -
DialUDP更适合客户端场景:比如向 NTP 服务器发请求,之后所有Write默认发给同一地址,Read只收该地址的响应 - UDP 没有内置重传和顺序保证,应用层若需可靠传输,得自己实现序列号、ACK 和重发逻辑
如何安全关闭正在运行的 net.Listener
直接调用 ln.Close() 不会中断已建立的 conn,但后续 Accept() 会返回 ErrClosed。真正的难点在于等待已有连接处理完成。
- 不要在
ln.Close()后立刻退出程序,否则活跃conn可能被强制断开 - 常用做法是用
sync.WaitGroup记录活跃连接数:每次Accept后wg.Add(1),handle结束前wg.Done() - 关闭流程应为:
ln.Close()→ 启动 goroutine 调用wg.Wait()→ 再退出;也可结合context.WithTimeout防止无限等待
net 包足够轻量,但也因此把很多责任留给了开发者:超时、重试、连接池、TLS 封装都得自己补全。一个小而完整的网络服务,往往 80% 的代码不在 Listen 或 Dial,而在围绕它们构建的错误恢复和资源管理逻辑里。











