如何获取公网IP?

15

想象这样一种情况,我有一台配备了两个网卡的电脑,一个连接到互联网,另一个连接到本地网络,如何使用 C# 检测连接到互联网的 IP?


值得记住的是您也可以有多个连接。 - Rowland Shaw
是的,但我只需要一个,主要的那个。 :) - Lukas Šalkauskas
4
您可能是指任意一个,因为除了在路由表中排在第一位之外没有其他定义“主”接口的标准。即使如此,操作系统也可以(并且确实会)选择使用第二个接口来平衡带宽利用。 - Hosam Aly
16个回答

13

试一下这个:

static IPAddress getInternetIPAddress()
{
    try
    {
        IPAddress[] addresses = Dns.GetHostAddresses(Dns.GetHostName());
        IPAddress gateway = IPAddress.Parse(getInternetGateway());
        return findMatch(addresses, gateway);
    }
    catch (FormatException e) { return null; }
}

static string getInternetGateway()
{
    using (Process tracert = new Process())
    {
        ProcessStartInfo startInfo = tracert.StartInfo;
        startInfo.FileName = "tracert.exe";
        startInfo.Arguments = "-h 1 208.77.188.166"; // www.example.com
        startInfo.UseShellExecute = false;
        startInfo.RedirectStandardOutput = true;
        tracert.Start();

        using (StreamReader reader = tracert.StandardOutput)
        {
            string line = "";
            for (int i = 0; i < 9; ++i)
                line = reader.ReadLine();
            line = line.Trim();
            return line.Substring(line.LastIndexOf(' ') + 1);
        }
    }
}

static IPAddress findMatch(IPAddress[] addresses, IPAddress gateway)
{
    byte[] gatewayBytes = gateway.GetAddressBytes();
    foreach (IPAddress ip in addresses)
    {
        byte[] ipBytes = ip.GetAddressBytes();
        if (ipBytes[0] == gatewayBytes[0]
            && ipBytes[1] == gatewayBytes[1]
            && ipBytes[2] == gatewayBytes[2])
        {
            return ip;
        }
    }
    return null;
}
请注意,此实现中的findMatch()函数依赖于类C匹配。如果要支持类B匹配,请省略对ipBytes[2] == gatewayBytes[2]的检查。
编辑历史:
  • 更新为使用www.example.com
  • 更新以包括getInternetIPAddress(),以展示如何使用其他方法。
  • 如果getInternetGateway()无法解析网关IP,则更新以捕获FormatException。(如果网关路由器配置为不响应traceroute请求,则可能会发生这种情况。)
  • 引用了Brian Rasmussen的评论。
  • 更新为使用www.example.com的IP地址,以便在DNS服务器关闭时也能正常工作。

@Brian,谢谢你的留言。我不知道为什么会有人这样做,但这是一个有效的观点。 - Hosam Aly
谢谢Hosam。我在这个帖子中使用了你的答案:http://stackoverflow.com/questions/19591675/find-out-the-active-local-ip/19591861#19591861,包括提到你是作者。 - Marco
@Serv 谢谢你告诉我。但是你用域名替换了example.com的IP,忽略了我回答中最后一条注释的重点。 - Hosam Aly
是我自己的错误,因为循环遇到了空异常,我一开始只改了那个而没有把循环计数器向下更改。 - Marco

11

我建议使用这段简单的代码,因为tracert并不总是有效,而whatsmyip.com也不是专门为此设计的:

private void GetIP()
{
    WebClient wc = new WebClient();
    string strIP = wc.DownloadString("http://checkip.dyndns.org");
    strIP = (new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b")).Match(strIP).Value;
    wc.Dispose();
    return strIP;
}

那是他想要获取的互联网地址,为什么这不起作用? - Hassen
正是我想要的!+1 - Gerardo Grignoli
1
这是一个棘手的解决方案,依赖于第三方。所以我们不会采用这种方法,但是感谢提供了一种思路。 - Romil Kumar Jain

9
互联网连接必须在与默认网关相同的IP网络上。从IP地址无法绝对确定是否能够访问“互联网”。基本上,你只能与自己的IP网络进行通信。其他所有通信都必须通过网关。因此,如果无法看到网关,则被限制在本地IP网络中。
然而,网关也依赖于其他网关,因此即使可以访问网关,也可能无法到达其他某些网络。这可能是由于过滤或缺少到所需网络的路由等原因。
实际上,在这种情况下谈论整个互联网几乎没有意义,因为你可能永远无法在任何时刻访问整个互联网。因此,请找出需要访问的内容并验证该网络的连通性。

我认为它是“作为互联网连接的默认网关”。我有一台机器,其中默认(主要)NIC连接到数据中心,而次要NIC则连接到互联网。因此,您应该选择将您连接到互联网的网关。 - Hosam Aly
1
重点是没有确切的方法可以确定,但你至少必须能够看到默认网关才能离开本地IP网络。 - Brian Rasmussen
我认为有一种方法可以确定。请看我的答案:https://dev59.com/KHRB5IYBdhLWcg3wyqAd - Hosam Aly
@Hosam - 我所指的“无法确定”的意思是,讨论互联网是否可以访问几乎没有意义,因为即使有外部访问,您可能仍无法访问所需的站点IP地址。 - Brian Rasmussen
@Brian,你说的对。我没有考虑到这种情况。但是OP问的是关于互联网连接的问题,所以我没有考虑到被封锁的网站。感谢澄清。 - Hosam Aly

6

这是我尝试获取默认IPv4地址的方法,而不必求助于DNS或调用像ipconfig和route这样的外部进程命令。希望下一个版本的.Net将提供访问Windows路由表的功能。

public static IPAddress GetDefaultIPv4Address()
{
    var adapters = from adapter in NetworkInterface.GetAllNetworkInterfaces()
                   where adapter.OperationalStatus == OperationalStatus.Up &&
                    adapter.Supports(NetworkInterfaceComponent.IPv4)
                    && adapter.GetIPProperties().GatewayAddresses.Count > 0 &&
                    adapter.GetIPProperties().GatewayAddresses[0].Address.ToString() != "0.0.0.0"
                   select adapter;

     if (adapters.Count() > 1)
     {
          throw new ApplicationException("The default IPv4 address could not be determined as there are two interfaces with gateways.");
     }
     else
     {
         UnicastIPAddressInformationCollection localIPs = adapters.First().GetIPProperties().UnicastAddresses;
         foreach (UnicastIPAddressInformation localIP in localIPs)
         {
            if (localIP.Address.AddressFamily == AddressFamily.InterNetwork &&
                !localIP.Address.ToString().StartsWith(LINK_LOCAL_BLOCK_PREFIX) &&
                !IPAddress.IsLoopback(localIP.Address))
            {
                return localIP.Address;
            }
        }
    }

    return null;
}

1
请问您能定义一下 LINK_LOCAL_BLOCK_PREFIX 吗?它是 "169." 吗? - jocull
这是最好的解决方案 <3 - jocull
运行良好,经过不同场景测试,是最推荐和首选的。 - Jsinh

2
一种简单的方法是获取并抓取众多“我的IP地址是什么”类型的网站之一。

1
这实际上是目前唯一正确的解决方案 - 如果您想以正确的方式执行此操作,您需要设置一个微小的Web服务,将调用IP地址回显给调用者。 - Ana Betts
@Paul:你的想法很好,但在NAT后面不起作用。 - Hosam Aly

2

我知道一个网络,所有的计算机都是双主机。具有互联网访问权限的网卡是172.22.x.x(私有IP),而私有局域网上的网卡是126.x.x.x(公共IP)。 - Gabe

1

这里有一篇可能会有帮助的文章:

如何在C#中检索“网络接口”

以下代码用于检索C#中的“网络接口”。您可以将“网络接口”识别为“网络和拨号连接”:您可以通过使用“开始>设置>网络和拨号连接”来访问它们。 C#没有提供检索此列表的简单方法。


1
我找到了一个解决方案:
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
Console.WriteLine(ipProperties.HostName);

        foreach (NetworkInterface networkCard in NetworkInterface.GetAllNetworkInterfaces())
        {
            foreach (GatewayIPAddressInformation gatewayAddr in networkCard.GetIPProperties().GatewayAddresses)
            {
                Console.WriteLine("Information: ");
                Console.WriteLine("Interface type: {0}", networkCard.NetworkInterfaceType.ToString());
                Console.WriteLine("Name: {0}", networkCard.Name);
                Console.WriteLine("Id: {0}", networkCard.Id);
                Console.WriteLine("Description: {0}", networkCard.Description);
                Console.WriteLine("Gateway address: {0}", gatewayAddr.Address.ToString());
                Console.WriteLine("IP: {0}", System.Net.Dns.GetHostByName(System.Net.Dns.GetHostName()).AddressList[0].ToString());
                Console.WriteLine("Speed: {0}", networkCard.Speed);
                Console.WriteLine("MAC: {0}", networkCard.GetPhysicalAddress().ToString());
            }
        }

1
那段代码没有返回与指定网络适配器相关联的IP地址 -- 对我来说,它返回了我的Windows Mobile设备的IP地址(而不是我的有线或Wi-Fi连接)。 - Rowland Shaw
当然可以,因为我选择了[0],但是你也可以通过网络接口的ID、名称、MAC地址或网关来选择它。 - Lukas Šalkauskas
但问题是“如何使用C#检测连接到互联网的IP地址?”,而这段代码并没有实现此功能。 - Rowland Shaw

0

跟我的朋友Lukas Šalkauskas的方向一样,我们写了这段代码:

   /* digged, built and (here and there) copied "as is" from web, without paying attention to style.
    Please, do not shot us for this. 
   */
public static void DisplayIPAddresses() 
    { 

        Console.WriteLine("\r\n****************************"); 
        Console.WriteLine("     IPAddresses"); 
        Console.WriteLine("****************************"); 


        StringBuilder sb = new StringBuilder(); 
        // Get a list of all network interfaces (usually one per network card, dialup, and VPN connection)      
        NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); 

        foreach (NetworkInterface network in networkInterfaces) 
        { 

            if (network.OperationalStatus == OperationalStatus.Up ) 
            { 
                if (network.NetworkInterfaceType == NetworkInterfaceType.Tunnel) continue; 
                if (network.NetworkInterfaceType == NetworkInterfaceType.Tunnel) continue; 
                //GatewayIPAddressInformationCollection GATE = network.GetIPProperties().GatewayAddresses; 
                // Read the IP configuration for each network    

                IPInterfaceProperties properties = network.GetIPProperties(); 
                //discard those who do not have a real gateaway  
                if (properties.GatewayAddresses.Count > 0) 
                { 
                    bool good = false; 
                    foreach  (GatewayIPAddressInformation gInfo in properties.GatewayAddresses) 
                    { 
                        //not a true gateaway (VmWare Lan) 
                        if (!gInfo.Address.ToString().Equals("0.0.0.0")) 
                        { 
                            sb.AppendLine(" GATEAWAY "+ gInfo.Address.ToString()); 
                            good = true; 
                            break; 
                        } 
                    } 
                    if (!good) 
                    { 
                        continue; 
                    } 
                } 
                else { 
                    continue; 
                } 
                // Each network interface may have multiple IP addresses        
                foreach (IPAddressInformation address in properties.UnicastAddresses) 
                { 
                    // We're only interested in IPv4 addresses for now        
                    if (address.Address.AddressFamily != AddressFamily.InterNetwork) continue; 

                    // Ignore loopback addresses (e.g., 127.0.0.1)     
                    if (IPAddress.IsLoopback(address.Address)) continue; 

                    if (!address.IsDnsEligible) continue; 
                    if (address.IsTransient) continue;  

                    sb.AppendLine(address.Address.ToString() + " (" + network.Name + ") nType:" + network.NetworkInterfaceType.ToString()     ); 
                } 
            } 
        } 
        Console.WriteLine(sb.ToString()); 
    }

0

这是一个获取系统默认连接到互联网时使用的公共IPv4或IPv6地址的2022实现。它使用 https://www.ipify.org 上当前免费的互联网服务/API 描述。基本上,他们的 API 只返回一个简单的字符串,里面只有你的IP地址。
请注意,我正在使用最新的 .Net SDK 进行构建(Aug '22)。可能需要做一些小的更改才能在旧版本上运行。

代码将会像这样使用:

IPAddress ipv4 = await WanIp.GetV4Async(); // return v4 address or IPAddress.None
IPAddress ipv6 = await WanIp.GetV6Async(); // return v6 address or IPAddress.None
IPAddress ip = await WanIp.GetIpAsync(); // might return either v4 or v6

using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Net.Threading.Tasks;

public class WanIp {
  private static readonly HttpClient httpClient = new();
  public static async Task<IPAddress> GetV4Async() => 
    await GetIpAsync(AddressFamily.InterNetwork);
  public static async Task<IPAddress> GetV6Async() => 
    await GetIpAsync(AddressFamily.InterNetworkV6);

  public static async Task<IPAddress> GetIpAsync(AddressFamily family = AddressFamily.Unspecified) {
    var uri = family switch {
      AddressFamily.InterNetwork => "https://api.ipify.org",
      AddressFamily.InterNetworkV6 => "https://api6.ipify.org",
      AddressFamily.Unspecified =>  "https://api64.ipify.org",
      _ => throw new ArgumentOutOfRangeException(nameof(family))
    };

    IPAddress ip;
    try {
      var ipString = await httpClient.GetStringAsync(uri);
      _ = IPAddress.TryParse(ipString, out ip);
    } catch (Exception ex) {  // If network down, etc.
      ip = IPAddress.None;
      // maybe throw an exception here if you like
    }

    // make sure we get family we asked for, or reply with none
    if (family != AddressFamily.Unspecified && ip.AddressFamily != family) ip = IPAddress.None;
    return ip;
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接