【问题标题】:Why Ping timeout is not working correctly?为什么 Ping 超时无法正常工作?
【发布时间】:2018-03-02 12:43:33
【问题描述】:

我有 5 台电脑,我想 ping 这台电脑是否可用。所以我正在使用 c# Ping 类。有两台电脑可用,但当我 ping 它们时,另外 3 台电脑已关闭,我的程序等待至少 7 秒的响应。

我只想检查 1000 毫秒并返回 OK 或 ERROR...

如何控制 ping 超时?

这是我的代码

        foreach (var item in listofpc)
        {
            Stopwatch timer = Stopwatch.StartNew();
            try
            {
                Ping myPing = new Ping();
                PingReply reply = myPing.Send(ServerName, 500);
                if (reply != null)
                {
                    timer.Stop();
                    TimeSpan timeTaken = timer.Elapsed;
                    Log.append("PING OK TimeTaken="+ timeTaken.ToString() + " Miliseconds", 50);
                }

            }
            catch (Exception ex)
            {
                timer.Stop();
                TimeSpan timeTaken = timer.Elapsed;
                Log.append("PING ERROR  TimeTaken=" +
                   timeTaken.ToString() + " Miliseconds \n" + ex.ToString(), 50);

            }
        }

但是当我检查我的日志时,我看到响应时间是 2 秒。为什么 ping 超时值不起作用?

有什么想法吗?

【问题讨论】:

  • 对不起,我删除了我之前的评论。那是错的。我被日志消息“PING OK”误导了,错过了您实际上没有测试 ping 回复状态,而是 ping.Send 是否返回 null。对不起,我的错:(
  • 没问题..你有什么建议吗? @elgonzo
  • 不,很遗憾没有。预计超时会有一点波动,但不会是原始超时周期的 4 倍。确实有点奇怪……
  • 我已经运行了一个快速测试 ping 一个不可用的 IP 地址 - 超时似乎有 500 毫秒的分辨率 - 超时时间比指定的超时时间快半秒,但永远不会超过指定的超时时间 (秒表的准确性)。 (快速返回一般是第一次ping,其余接近超时值)你的日志是报PING OK还是PING ERROR。注意要确定 ping 是否成功,您需要检查 reply.Status。
  • Ping.Send() 有一个重载,让您指定System.Net.NetworkInformation.PingOptions。保持 TTL 最大值为 64(也可以尝试 32)和DontFragment = true。缓冲区必须手动初始化。使用byte[] b = new byte[32];:32 字节是标准。对于 1000 毫秒的超时,ttl=64 应该没问题。

标签: c# timeout ping


【解决方案1】:

更新:原因:正如其他人所说,Ping 超时不起作用的最可能原因是 DNS 解析。对 getaddrinfo 的系统调用(Dns.GetHostAddresses 和 Ping 使用的系统调用 - https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo、https://docs.microsoft.com/en-us/dotnet/api/system.net.dns.gethostaddresses?view=netcore-3.1 - 与完整框架类似)不接受超时。因此,对以下代码的进一步改进是将 dns 查找与 ping 分开。首先使用类似于以下代码的超时方法进行查找,并且仅 ping IP 而不是主机名,并指定超时。

我过去也遇到过类似的问题,我有一些代码可能有助于解决这个问题。我在这里编辑它,所以它可能没有 100% 正确,而且比你的需要复杂一点。可以试试这样的吗?

锤子:(完整的代码和测试结果也包括在下面)

private static PingReply ForcePingTimeoutWithThreads(string hostname, int timeout)
{
    PingReply reply = null;
    var a = new Thread(() => reply =  normalPing(hostname, timeout));
    a.Start();
    a.Join(timeout); //or a.Abort() after a timeout, but you have to take care of a ThreadAbortException in that case... brrr I like to think that the ping might go on and be successful in life with .Join :)
    return reply;
}

private static PingReply normalPing(string hostname, int timeout)
{
   try
   {
      return new Ping().Send(hostname, timeout);
   }
   catch //never do this kids, this is just a demo of a concept! Always log exceptions!
   {
      return null; //or this, in such a low level method 99 cases out of 100, just let the exception bubble up
    }
 }

这是一个完整的工作示例(Tasks.WhenAny 经过测试并在 4.5.2 版中工作)。我还了解到,Tasks 的优雅对性能的影响比我记得的要大,但是 Thread.Join/Abort 对于大多数生产环境来说太残酷了。

using System;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        //this can easily be async Task<PingReply> or even made generic (original version was), but I wanted to be able to test all versions with the same code
        private static PingReply PingOrTimeout(string hostname, int timeOut)
        {
            PingReply result = null;
            var cancellationTokenSource = new CancellationTokenSource();
            var timeoutTask = Task.Delay(timeOut, cancellationTokenSource.Token);

            var actionTask = Task.Factory.StartNew(() =>
            {
                result = normalPing(hostname, timeOut);
            }, cancellationTokenSource.Token);

            Task.WhenAny(actionTask, timeoutTask).ContinueWith(t =>
            {
                cancellationTokenSource.Cancel();
            }).Wait(); //if async, remove the .Wait() and await instead!
            
            return result;
        }

        private static PingReply normalPing(string hostname, int timeout)
        {
            try
            {
                return new Ping().Send(hostname, timeout);
            }
            catch //never do this kids, this is just a demo of a concept! Always log exceptions!
            {
                return null; //or this, in such a low level method 99 cases out of 100, just let the exception bubble up
            }
        }

        private static PingReply ForcePingTimeoutWithThreads(string hostname, int timeout)
        {
            PingReply reply = null;
            var a = new Thread(() => reply =  normalPing(hostname, timeout));
            a.Start();
            a.Join(timeout); //or a.Abort() after a timeout... brrr I like to think that the ping might go on and be successful in life with .Join :)
            return reply;
        }

        static byte[] b = new byte[32];
        static PingOptions po = new PingOptions(64, true);
        static PingReply JimiPing(string hostname, int timeout)
        {
            try
            {
                return new Ping().Send(hostname, timeout, b, po);
            }
            catch //never do this kids, this is just a demo of a concept! Always log exceptions!
            {
                return null; //or this, in such a low level method 99 cases out of 100, just let the exception bubble up
            }
        }

        static void RunTests(Func<string, int, PingReply> timeOutPinger)
        {
            var stopWatch = Stopwatch.StartNew();
            var expectedFail = timeOutPinger("bogusdjfkhkjh", 200);
            Console.WriteLine($"{stopWatch.Elapsed.TotalMilliseconds} false={expectedFail != null}");
            stopWatch = Stopwatch.StartNew();
            var expectedSuccess = timeOutPinger("127.0.0.1", 200);
            Console.WriteLine($"{stopWatch.Elapsed.TotalMilliseconds} true={expectedSuccess != null && expectedSuccess.Status == IPStatus.Success}");
        }

        static void Main(string[] args)
        {
            RunTests(normalPing);
            RunTests(PingOrTimeout);
            RunTests(ForcePingTimeoutWithThreads);
            RunTests(JimiPing);
            
            Console.ReadKey(false);
        }
    }
}

我的一些测试结果:

>Running ping timeout tests timeout = 200. method=normal
>
> - host: bogusdjfkhkjh elapsed: 2366,9714 expected: false=False
> - host: 127.0.0.1 elapsed: 4,7249 expected: true=True
>
>Running ping timeout tests timeout = 200. method:ttl+donotfragment (Jimi)
>
> - host: bogusdjfkhkjh elapsed: 2310,836 expected: false actual: False
> - host: 127.0.0.1 elapsed: 0,7838 expected: true actual: True
>
>Running ping timeout tests timeout = 200. method:tasks
>
> - host: bogusdjfkhkjh elapsed: 234,1491 expected: false actual: False
> - host: 127.0.0.1 elapsed: 3,2829 expected: true=True
>
>Running ping timeout tests timeout = 200. method:threads
>
> - host: bogusdjfkhkjh elapsed: 200,5357 expected: false actual:False
> - host: 127.0.0.1 elapsed: 5,5956 expected: true actual: True

注意 对于 Tasks 版本,即使调用线程“未阻塞”,操作本身(在本例中为 ping)也可能会一直持续到实际超时。这就是为什么我建议也为 ping 命令本身设置一个超时时间。

更新也在研究原因,但目前认为解决方法会对您有所帮助。

新发现:

【讨论】:

  • 我想试一试,但是当我复制和粘贴时,我看到 Task 没有 Delay() 函数和 whenany()
  • 将 using 添加到 System.Threading.Tasks 我认为(从 iPhone 发送)将在 1 小时内到达家中,我可以帮助您运行它!
  • 我已经添加了,但我没有看到实例我正在等待你的帮助谢谢@AlexC。
  • 好的,我在家。现在忘记了工作中的笔记本电脑充电器,所以让我们快速行动。你的目标是什么框架版本? @saulyasar 可以使用线程而不是任务和异步/等待来完成类似的方法,但在我看来它会失去一些美感:)
  • 我编辑了我的答案以包含一个完整的工作示例,以 4.5.2 版本为目标
【解决方案2】:

System.Net.NetworkInformation.Ping 的这个实现已经过测试
框架 4.0/4.5.1/4.7.1、控制台和 Winforms 版本。

结果始终相同(如下所述)。

这是IcmpSendEcho2 和Icmp6SendEcho2 的.NET Framework Ping() 实现

同步版本(输出类型为控制台,但不相关):

(这个方法的原始版本不返回IPStatus。它 返回具有完整异常信息的类对象。主机名 或地址已通过 DNS 解析器进行验证/翻译:
IPAddress _HostIPAddress = Dns.GetHostAddresses(HostAddress).First(); 如果未找到主机并且 No such host is known 通知,则会抛出 SocketException。

em>Result: BadDestination 在这里设置了未知主机,仅用于此测试)。

static void Main(string[] args)
{
    List<string> HostAdrr = new List<string>() { "192.168.2.1", "192.168.2.201", 
                                                 "192.168.1.99", "200.1.1.1", 
                                                 "www.microsoft.com", "www.hfkhkhfhkf.com" };
    IPStatus _result;;
    foreach (string _Host in HostAdrr)
    {
        Stopwatch timer = Stopwatch.StartNew();
        _result = PingHostAddress(_Host, 1000);
        timer.Stop();
        Console.WriteLine("Host: {0}  Elapsed time: {1}ms  Result: {2}", _Host, timer.ElapsedMilliseconds, _result);
        Console.WriteLine();
    }
    Console.ReadLine();
}

public static IPStatus PingHostAddress(string HostAddress, int timeout)
{
    if (string.IsNullOrEmpty(HostAddress.Trim()))
        return IPStatus.BadDestination;

    byte[] buffer = new byte[32];
    PingReply iReplay = null;
    using (Ping iPing = new Ping())
    {
        try
        {
            //IPAddress _HostIPAddress = Dns.GetHostAddresses(HostAddress).First();
            iReplay = iPing.Send(HostAddress,
                                    timeout,
                                    buffer,
                                    new PingOptions(64, true));
        }
        catch (FormatException)
        {
            return IPStatus.BadDestination;
        }
        catch (NotSupportedException nsex)
        {
            throw nsex;
        }
        catch (PingException pex)
        {
            //Log/Manage pex
        }
        //catch (SocketException soex)
        //{
        //
        //}
        catch (Exception ex)
        {
            //Log/Manage ex
        }
        return (iReplay != null) ? iReplay.Status : IPStatus.BadDestination;
    }
}

异步版本使用.SendPingAsync() 方法并具有通常的异步签名。

public async Task<IPStatus> PingHostAddressAsync(string HostAddress, int timeout)
{
    //(...)
    iReplay = await iPing.SendPingAsync(HostAddress,
                                        timeout,
                                        buffer,
                                        new PingOptions(64, false));
    //(...)
}

使用异步版本时结果不会改变。用 Winform 测试。不管人们多么想弄乱用户界面。

如何解释结果:

参数:
- 由Ping.Send() 方法解析的主机名。 (预先解析主机名不会改变结果)
- 超时: 1000 毫秒(也测试了 500 毫秒和 2000 毫秒)
- 缓冲区:标准 32 字节
- TTL: 64
- 不要碎片化:正确

主机:192.168.2.1 => 同一网络中的可访问主机。
Host: 192.168.2.201 => Unreachable (off) Host in a reachable 不同的子网。
主机:192.168.1.99 => 可访问的不同网络中不存在主机(硬件路由)
主机:200.1.1.1 => 不存在的 Internet 地址
主机:www.microsoft.com => 可访问的现有已解析 Internet 主机名
主机:www.hfkhkhfhkf.com => 不存在无法解析的 Internet 主机名

Host: 192.168.2.1  Elapsed time: 4  Result: Success

Host: 192.168.2.201  Elapsed time: 991  Result: TimedOut

Host: 192.168.1.99  Elapsed time: 993  Result: TimedOut

Host: 200.1.1.1  Elapsed time: 997  Result: TimedOut

Host: www.microsoft.com  Elapsed time: 57  Result: Success

Host: www.hfkhkhfhkf.com  Elapsed time: 72  Result: BadDestination


正如@PaulF 在 cmets 中所指出的,唯一的(持续的)异常 是主机不可访问时的第一个响应:总是有点 比规定的超时时间短。但总是遵守 Timeout(Ping 方法总是在设定的时间间隔内返回)。

【讨论】:

  • 您是否尝试过使用“hfkhkhfhkf”而不是“www.hfkhkhfhkf.com”?
  • 我仍然可以使用此代码获得 2 秒的时间,将超时设置为 200 以用于上述格式的不存在的主机名。在 3 个独立的网络上进行了测试。
  • 好吧,第一个“地址”不会通过 IP 解析器。将返回“BadHostAddress”。大约2秒。总是2秒?这很奇特。您是否按原样复制了代码?我的意思是,要在同一页上。如果有,请尝试启用Network Tracing 以查看结果。此外,尝试手动设置(如果可能)在互联网暴露的路由器/dsl/hub/hug 中的 Google DNS 服务器(如果这不是一个选项,甚至在本地设置)。一个非常慢的 DNS 响应实际上可能会在这里造成麻烦。
  • "hgfdhgf" 是一个完全有效的主机名 :) 我认为这种格式可能存在一些向后兼容性/后备问题。我怀疑这就是 OP 遇到的问题
  • 是的,它是一个完全有效的 local 机器名。在我可以测试的网络中,它立即空手而归。但如果 DNS 服务器设置为递归,它会尝试反向查找地址(不回答 PTR 查询)。结果是不可预测的。 -- 本地网络中的Ping方法,IP地址和主机名,你有没有这些结果?如果是这样,我目前没有任何解释。它不应该发生(我不记得见过)。
【解决方案3】:

ping 到 8.8.8.8 之类的 IP 地址绝对可以正常工作,但是当 ping 到 www.google.com 之类的 dns 地址时,会出现随机超时。
我认为这些随机超时与 dns 解析有关,与 ping 超时无关

    private static bool DoPing()
    {
        try
        {
            using (System.Net.NetworkInformation.Ping ping = new Ping())
            {

                PingReply result = ping.Send("8.8.8.8", 500, new byte[32], new PingOptions { DontFragment = true, Ttl = 32 });

                if (result.Status == IPStatus.Success)
                    return true;
                return false;
            }
        }
        catch
        {
            return false;
        }
    }

【讨论】:

    猜你喜欢
    • 2011-07-24
    • 2017-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    • 2021-05-23
    相关资源
    最近更新 更多