【问题标题】:GetHostEntry is very slowGetHostEntry 很慢
【发布时间】:2009-06-15 16:26:31
【问题描述】:

我有一个 WinForms 应用程序,我正在尝试获取表单上显示的 IP 列表的反向 DNS 条目。

我遇到的主要问题是 System.Net.Dns.GetHostEntry 非常慢,尤其是在没有找到反向 DNS 条目时。使用直接 DNS,这应该很快,因为 DNS 服务器将返回 NXDOMAIN。在内部,它调用ws2_32.dll getnameinfo(),其中指出“名称解析可以通过域名系统 (DNS)、本地主机文件或其他命名机制” - 所以我假设是那些“其他命名机制”导致这么慢,但有谁知道这些机制是什么?

通常每个 IP 需要 5 秒,除非它找到反向条目,然后它几乎是立即的。我已经部分地使用线程解决了这个问题,但由于我正在做一个很大的列表并且我一次只能运行这么多线程,所以仍然需要一段时间才能完成所有这些。

有没有更好的方法可以更快地找到反向 DNS 条目?

【问题讨论】:

    标签: .net dns


    【解决方案1】:

    也许这会有所帮助? WayBack Machine 版本的dead link.

    (Dead link: http://www.chapleau.info/blog/2008/09/09/reverse-dns-lookup-with-timeout-in-c.html)

    代码,供后人参考:

    private delegate IPHostEntry GetHostEntryHandler(string ip);
    
    public string GetReverseDNS(string ip, int timeout)
    {
        try
        {
            GetHostEntryHandler callback = new GetHostEntryHandler(Dns.GetHostEntry);
            IAsyncResult result = callback.BeginInvoke(ip,null,null);
            if (result.AsyncWaitHandle.WaitOne(timeout, false))
            {
                return callback.EndInvoke(result).HostName;
            }
            else
            {
                return ip;
            }
        }
        catch (Exception)
        {
            return ip;
        }
    }
    

    【讨论】:

    • 不确定这在发布时是否有效,现在在 Windows 10 上执行时返回“此平台不支持操作”
    【解决方案2】:

    不幸的是,没有办法(我知道)在客户端的 Windows API 中更改此超时。您可以做的最好的事情是编辑注册表以更改 DNS 查询中的超时长度。有关详细信息,请参阅this technet article。据我所知,执行此操作时会运行尝试 1、2 和 3,因此会延迟 5 秒。

    唯一的其他选择是使用某种形式的后台处理,例如反向 DNS 查找的 asynchronous version。但是,这将使用线程,因此您最终会遇到超时(这会更好,因为它会跨越许多等待线程,但仍然不完美)。就个人而言,如果您要处理大量数字,我会混合使用这两种方法 - 异步执行反向查找并修改注册表以缩短超时时间。


    在 cmets 之后编辑:

    如果您查看getnameinfo 上的标志,则有一个标志参数。我相信您可以对此进行 P/Invoke 并设置标志 NI_NAMEREQD | NI_NUMERICHOST 以获得您所追求的行为。 (第一个说如果没有 DNS 条目,则立即出错,这有助于超时 - 第二个说进行反向查找。)

    【讨论】:

    • 我实际上确实使用了那个版本。它有效地解决了超时问题。我的问题更多是必须有一个超时。使用一些随机 IP 在命令行上运行 nslookup 或 dig - 它通常会在
    • 我相信你可以通过 P/Invoke 完成你想要的,通过使用与 getnameinfo 上的默认值不同的标志。查看我的编辑。
    【解决方案3】:

    您可以通过查询in-addr.arpa 域来显着提高查找失败的速度。例如,要对 IP 地址 A.B.C.D 执行反向 IP 查找,您应该查询域 D.C.B.A.in-addr.arpa 的 DNS。如果可以进行反向查找,则返回带有主机名的 PTR 记录。

    很遗憾,.NET 没有用于查询 DNS 的通用 API。但是通过使用 P/Invoke,您可以调用 DNS API 来获得所需的结果(如果反向查找失败,该函数将返回 null)。

    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Net;
    using System.Runtime.InteropServices;
    
    public static String ReverseIPLookup(IPAddress ipAddress) {
      if (ipAddress.AddressFamily != AddressFamily.InterNetwork)
        throw new ArgumentException("IP address is not IPv4.", "ipAddress");
      var domain = String.Join(
        ".", ipAddress.GetAddressBytes().Reverse().Select(b => b.ToString())
      ) + ".in-addr.arpa";
      return DnsGetPtrRecord(domain);
    }
    
    static String DnsGetPtrRecord(String domain) {
      const Int16 DNS_TYPE_PTR = 0x000C;
      const Int32 DNS_QUERY_STANDARD = 0x00000000;
      const Int32 DNS_ERROR_RCODE_NAME_ERROR = 9003;
      IntPtr queryResultSet = IntPtr.Zero;
      try {
        var dnsStatus = DnsQuery(
          domain,
          DNS_TYPE_PTR,
          DNS_QUERY_STANDARD,
          IntPtr.Zero,
          ref queryResultSet,
          IntPtr.Zero
        );
        if (dnsStatus == DNS_ERROR_RCODE_NAME_ERROR)
          return null;
        if (dnsStatus != 0)
          throw new Win32Exception(dnsStatus);
        DnsRecordPtr dnsRecordPtr;
        for (var pointer = queryResultSet; pointer != IntPtr.Zero; pointer = dnsRecordPtr.pNext) {
          dnsRecordPtr = (DnsRecordPtr) Marshal.PtrToStructure(pointer, typeof(DnsRecordPtr));
          if (dnsRecordPtr.wType == DNS_TYPE_PTR)
            return Marshal.PtrToStringUni(dnsRecordPtr.pNameHost);
        }
        return null;
      }
      finally {
        const Int32 DnsFreeRecordList = 1;
        if (queryResultSet != IntPtr.Zero)
          DnsRecordListFree(queryResultSet, DnsFreeRecordList);
      }
    }
    
    [DllImport("Dnsapi.dll", EntryPoint = "DnsQuery_W", ExactSpelling=true, CharSet = CharSet.Unicode, SetLastError = true)]
    static extern Int32 DnsQuery(String lpstrName, Int16 wType, Int32 options, IntPtr pExtra, ref IntPtr ppQueryResultsSet, IntPtr pReserved);
    
    [DllImport("Dnsapi.dll", SetLastError = true)]
    static extern void DnsRecordListFree(IntPtr pRecordList, Int32 freeType);
    
    [StructLayout(LayoutKind.Sequential)]
    struct DnsRecordPtr {
      public IntPtr pNext;
      public String pName;
      public Int16 wType;
      public Int16 wDataLength;
      public Int32 flags;
      public Int32 dwTtl;
      public Int32 dwReserved;
      public IntPtr pNameHost;
    }
    

    【讨论】:

    • 这对于一个可解析的地址工作得很好,但是当我给它一个不可解析的地址时它挂了。
    • 在 Windows 上,使用“a.b.c.d”表示法时,DnsQuery() 将返回状态 9003(无 DNS 记录),但使用“d.c.b.a.in-addr.arpa”表示法会成功。
    【解决方案4】:

    万一有人碰到这个......

    我从使用 TcpClient 构造函数切换到调用过时的Dns.GetHostByName

    无论出于何种原因,它的性能都要好得多。

    public TcpClientIP(string hostname, int port) : base()
    {
        try
        {
            if (_legacyDnsEnabled)
            {
                var host = Dns.GetHostByName(hostname);
                var ips = host.AddressList.Select(o => new IPAddress(o.GetAddressBytes())).ToArray();
                Connect(ips, port);
                return;
            }
        }
        catch(SocketException e)
        { }
    
        Connect(hostname, port);
    }
    

    【讨论】:

    • 这仅适用于您已经有主机名的情况,而不是如果您有 IP 地址并想要原始问题中的主机名
    • @Steve 可能意味着 Dns.GetHostByAddress(IpAddress)。这可能会做你想做的事,而且速度更快。在 Windows 10 上 Dns.GetHostEntry() 对我来说需要 12 秒,而 Dns.GetHostByAddress() 需要不到 2 秒。
    • 在我的系统上涉及两个网卡,其中第二个没有 DNS,超时是相同的。
    【解决方案5】:

    主要是添加评论,以防有人通过谷歌找到这个,就像我一样......

    行为可能因操作系统版本而异;这些说明适用于 Server 2008 R2。

    NI_NUMERICHOST 标志不能满足您的要求;这种情况下,API 会返回主机标识符的数字版本(即:IP 地址),而不是主机名。

    即使使用 NI_NAMEREQD,如果找不到信息,仍然会超时(默认为 5 秒)。我不确定这是否是由于级联查找超时或其他原因,但此标志不会阻止超时(据我所知,任何其他标志也不会)。

    这似乎在内部调用了 WSALookupService API,尽管尚不清楚传递了哪些标志。另请注意,返回的信息可能不正确;在我的一个测试用例中,nslookup 没有返回任何结果,但 getnameinfo 以不准确和不合格的名称返回。所以...是的,还没有好的答案,但希望这些信息对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-22
      • 2011-03-08
      • 2013-02-23
      • 2015-12-24
      • 2020-10-23
      相关资源
      最近更新 更多