最直接测 RTT 的方法是用 net.DialTimeout + time.Now() 手动计时,它测量 TCP 三次握手耗时,适用于端口开放场景,不依赖系统 ping 或 root 权限,但非 ICMP 层延迟。

用 net.DialTimeout + 手动计时测 RTT 最直接
Go 标准库没有内置的 ping 或 RTT 测量函数,net.DialTimeout 是最轻量、最可控的起点——它只管建连阶段,而 TCP 握手完成那一刻,基本就对应了 SYN → SYN-ACK → ACK 的往返耗时(即近似 RTT)。
注意:这不是 ICMP ping,不依赖系统 ping 命令,也不需要 root 权限,适合容器或受限环境。
- 只适用于目标端口开放且接受 TCP 连接的场景(比如 HTTP 服务的 80/443、数据库的 3306)
- 如果目标端口被防火墙拦截(如 RST 响应)或完全无响应(超时),测得的是连接失败耗时,不是真实 RTT
- 别用
time.AfterFunc或 goroutine 等待,直接用time.Now()套住net.DialTimeout调用更准
start := time.Now()
conn, err := net.DialTimeout("tcp", "example.com:443", 3*time.Second)
if err != nil {
fmt.Printf("failed after %v: %v\n", time.Since(start), err)
} else {
conn.Close()
fmt.Printf("RTT ≈ %v\n", time.Since(start))
}
想模拟真实 ping?用 exec.Command 调系统 ping
要测 ICMP 层 RTT(比如判断网络层是否通畅、绕过应用层防火墙),只能借系统 ping。Go 本身不提供 ICMP socket 操作(标准库无 net.IPConn.WriteTo 的 ICMP 支持),强行用 syscall 写太重,也跨平台困难。
Linux/macOS 下 ping -c 1 -W 2 example.com 输出稳定;Windows 是 ping -n 1 -w 2000 example.com,参数和单位都不同。
立即学习“go语言免费学习笔记(深入)”;
- 必须解析命令输出,重点抓
time=后面的毫秒值(Linux/macOS)或时间=(中文 Windows) - 超时错误不是
exit status 1,而是exit status 512(macOS)或exit status 1(Linux),取决于是否收到任何响应 - 别忽略
stderr,某些系统把延迟信息写在 stderr(如 macOS)
golang.org/x/net/icmp 能做真 ICMP,但限制多
这个扩展包支持构造和解析 ICMP 包,但实际用起来有硬门槛:
- Linux 需要
sudo setcap cap_net_raw+ep ./your-binary,否则socket: operation not permitted - macOS 默认禁用 raw socket,需启动时加
--allow-raw-sockets(仅 Go 1.21+ 且非 release build) - Windows 上几乎不可用(依赖 WinPCAP/Npcap,且包未适配)
- 你得自己处理校验和、序列号、超时重传逻辑,
icmp.ListenPacket只是基础收发,不封装 ping 语义
除非你在写网络诊断工具且能控制部署环境,否则不推荐从零撸 ICMP。
别踩这些坑:RTT 不等于应用层延迟
很多人测完 net.DialTimeout 就以为得到了“访问延迟”,其实这只是链路建立时间。后续 TLS 握手、HTTP 请求头发送、服务端处理、响应返回,全都不包含在内。
- 测 HTTPS 服务,用
net.DialTimeout测 443 得到的是 TCP 握手 RTT;加上tls.Client的Handshake耗时,才是完整 TLS 连接延迟 - 同一台机器反复测,要注意本地 TCP TIME_WAIT 积压导致后续连接变慢(尤其短连接高频调用)
- 用
context.WithTimeout包裹net.DialContext比DialTimeout更灵活,但底层仍是同个机制,别误以为它能测出更细粒度的延迟
真正要定位慢在哪一层,得拆开测:TCP 建连、TLS 握手、首字节响应(TTFB)、内容下载……每个环节独立计时,才不会把 DNS 解析慢当成网络延迟。










