用 Ping 类发 ICMP 请求需 new 实例后调用 SendAsync(推荐)或 Send,目标建议用 IP 如“8.8.8.8”避免 DNS 失败;Reply.Status 有十余种状态,需区分处理;单次延迟波动大,应多次测量;Linux/macOS 需 root 权限;TCP 连接测延时更可靠,尤其在 ICMP 被屏蔽环境。

怎么用 Ping 类发一次 ICMP 请求
直接 new 一个 Ping 实例,调用 Send 或 SendAsync 即可拿到延迟值。注意它默认不走超时重试逻辑,一次就是一次——失败就真失败了。
-
Ping是 .NET Framework 2.0 就有的类,.NET Core 2.1+ 和 .NET 5+ 都支持,但 Linux/macOS 上需要 root 权限才能发 ICMP 包(普通用户会抛Operation not permitted) - 推荐优先用
SendAsync,避免阻塞线程;同步版Send在高并发下容易拖垮线程池 - 目标地址写
"8.8.8.8"比写域名更稳,DNS 解析失败会导致整个 ping 失败,且不体现在PingReply.Status里
Ping p = new Ping();
PingReply reply = await p.SendAsync("8.8.8.8", 1000); // 1000ms 超时
if (reply.Status == IPStatus.Success) {
Console.WriteLine($"延迟:{reply.RoundtripTime}ms");
}
PingReply.Status 不是只有 Success 和 Failed
很多人只检查 reply.Status == IPStatus.Success,但实际还有十来种状态,比如 TtlExpired、DestinationHostUnreachable、BadDestination,它们都算“没通”,但原因完全不同——网络层不通、路由问题、防火墙拦截、目标关机,处理方式应有区别。
-
Timeout最常见,说明包发出去了但没回,不等于网络断了(可能是中间设备丢包、ICMP 被禁) -
DestinationNetworkUnreachable或DestinationHostUnreachable通常意味着路由表没这条路,或者网关明确拒绝了 -
TimedOut和UnknownError很容易被当成同一类错误,但后者常因权限不足(Linux)、ICMP 被系统级屏蔽、或Ping对象被提前 dispose 导致
为什么连续 ping 几次结果差异很大?
单次 Ping 的 RoundtripTime 波动大,不是代码写错了,而是 ICMP 包本身不保证 QoS,受系统调度、网络队列、中间设备策略影响极大。
- 别拿单次结果判断“网络好不好”,至少打 3–5 次取平均 + 看丢包率;
Ping类本身不提供批量统计,得自己循环 + 记录PingReply - Windows 默认 ping 用的是低优先级 ICMP 包,某些路由器会优先丢弃;Linux 下若没加
cap_net_raw+ep权限,可能根本发不出 - 如果发现
RoundtripTime突然从 20ms 跳到 1200ms,大概率是某跳路由做了限速或路径切换,不是你的代码问题
替代方案:不用 ICMP,改用 TCP 连接测延迟行不行?
可以,而且更可靠——尤其在企业内网或云环境,ICMP 经常被安全组/防火墙默认拦截,但 TcpClient.ConnectAsync 连某个开放端口(如 443)反而成功率更高。
- 用
TcpClient测延迟本质是测 TCP 握手耗时,和 ICMP 的意义不同,但对“服务是否可达+响应快慢”这个目标更贴近真实业务 - 注意别连错端口:连
8.8.8.8:80会失败(Google DNS 不开 HTTP),换成8.8.8.8:443或1.1.1.1:853(DoT)更合理 - 超时必须设,否则
ConnectAsync可能卡住 20+ 秒;建议用CancellationToken控制,别依赖Task.Wait(1000)
using var client = new TcpClient();
var cts = new CancellationTokenSource(1000);
await client.ConnectAsync("8.8.8.8", 443, cts.Token); // 成功即代表通,耗时可从 Stopwatch 获取
真实网络诊断里,ICMP 延迟只是起点,不是终点。很多人卡在“明明 ping 得通,但 HttpClient 调不通”,这时候再盯着 RoundtripTime 就跑偏了。










