TCP连接延迟应测三次握手耗时,需分离DNS解析与TCP建连,用net.DialTimeout配IP+端口;ICMP用exec.Command调系统ping并兼容多平台参数;并发需worker节流+context超时控制;延迟存float64,关注P99而非均值。

用 net.DialTimeout 测单次 TCP 连接延迟
真实网络延迟测试,不能只看 ICMP(ping),很多服务走的是 TCP,比如 API 接口、数据库连接。Go 标准库没有内置 ping 工具,但 net.DialTimeout 能直接模拟客户端建连行为,返回的耗时就是 TCP 三次握手完成时间。
常见错误是忽略 DNS 解析开销——net.DialTimeout 默认会先做 DNS 查询,如果目标域名解析慢,测出来就不是纯网络延迟。要分离 DNS 和 TCP,得先用 net.DefaultResolver.LookupHost 单独测一次,再用 net.DialTimeout 对 IP + 端口调用。
- 端口必须指定,比如
"80"或"443";传空字符串或"0"会 panic - 超时建议设为 5–10 秒,太短容易误判,太长拖慢批量测试
- 不要复用
net.Conn对象来“测多次”——每次Dial都是新连接,这才是真实场景
用 exec.Command 调用系统 ping 获取 ICMP 延迟
虽然 Go 不原生支持 ICMP,但多数 Linux/macOS/Windows 环境都有 ping 命令,直接调用更贴近运维习惯,也方便比对。
关键点在于参数兼容性:ping -c 3 -W 2 example.com 在 Linux 有效,但 macOS 的 -W 是毫秒级,且不支持 -c,得用 -c 3 + -t 2(macOS 用 -t,Linux 用 -W)。Windows 更麻烦,ping -n 3 -w 2000,单位是毫秒,且输出格式完全不同。
立即学习“go语言免费学习笔记(深入)”;
- 务必用
cmd.CombinedOutput()拿完整输出,否则 stderr 丢失,解析不到延迟值 - 正则匹配延迟推荐用
time=(\d+\.?\d*)\s*ms,但 Windows 输出可能是time 或time=1ms,需额外处理 - 避免并发大量
exec.Command,进程创建开销大,100 并发可能触发系统 fork 失败
批量测延迟时用 sync.WaitGroup + time.AfterFunc 控制总耗时
跑 50 个目标各测 10 次,若不做节流,瞬间发起 500 个连接,本地端口耗尽、对方限频、甚至被当攻击,结果失真。
真正有效的节流不是简单加 time.Sleep,而是用 sync.WaitGroup 配合固定 worker 数(比如 10 个 goroutine),每个 worker 循环取任务;同时用 time.AfterFunc 设置全局超时(如 30 秒),超时后主动 cancel 所有 pending 的 Dial 或 exec。
-
context.WithTimeout对net.DialContext有效,但对exec.Command无效——后者需手动调cmd.Process.Kill() - 记录结果时别用全局 map + 锁,改用 channel 收集,避免写冲突和锁竞争
- 延迟值建议存为
float64(单位 ms),别转成字符串再 parse,损耗精度还慢
延迟数据不稳定?检查 GODEBUG 和系统 socket 缓冲区
实测中发现同一台机器、同一目标,连续 5 次延迟从 12ms 跳到 210ms,排除网络问题后,定位到 Go 运行时默认启用 netpoll,在高并发短连接下可能因 epoll/kqueue 事件积压导致延迟抖动。
临时缓解可加环境变量 GODEBUG=netdns=cgo 强制走 cgo DNS(更稳),或 GODEBUG=asyncpreemptoff=1 关闭异步抢占(减少调度延迟),但这只是调试手段,不能上生产。
- 更根本的是调大系统 socket 缓冲区:
sysctl -w net.core.somaxconn=65535,尤其在 Linux 上跑大量并发连接时 - Go 1.21+ 支持
net.ListenConfig.Control设置 socket 选项,可显式调setsockopt(SO_RCVBUF),但普通延迟工具没必要这么重 - 别信 “平均延迟低就代表网络好”——P99 延迟才是瓶颈指标,记得把原始数据存下来算分位数










