【问题标题】:How can I get the IP Address of a local computer?如何获取本地计算机的 IP 地址?
【发布时间】:2010-09-12 10:34:29
【问题描述】:

在 C++ 中,获取本地计算机的 IP 地址和子网掩码最简单的方法是什么?

我希望能够在我的本地网络中检测本地机器的 IP 地址。在我的特殊情况下,我有一个子网掩码为 255.255.255.0 的网络,我的计算机的 IP 地址是 192.168.0.5。我需要以编程方式获取这些有两个值,以便向我的网络发送广播消息(对于我的特殊情况,格式为 192.168.0.255)

编辑:许多答案没有给出我预期的结果,因为我有两个不同的网络 IP。 Torial 的代码成功了(它给了我两个 IP 地址)。

编辑 2:感谢 Brian R. Bondy 提供有关子网掩码的信息。

【问题讨论】:

  • Re: 169.254.47.253,看起来你没有路由器,那是你的外部地址。

标签: c++ sockets networking


【解决方案1】:

这个问题比看起来更棘手,因为在许多情况下,没有“本地计算机的 IP 地址”,而是有许多不同的 IP 地址。例如,我现在正在输入的 Mac(这是一个非常基本的标准 Mac 设置)具有以下关联的 IP 地址:

fe80::1%lo0  
127.0.0.1 
::1 
fe80::21f:5bff:fe3f:1b36%en1 
10.0.0.138 
172.16.175.1
192.168.27.1

...这不仅仅是弄清楚以上哪个是“真正的IP地址”的问题,或者...它们都是“真实的”和有用的;有些比其他的更有用,具体取决于您要使用这些地址的目的。

根据我的经验,为您的本地计算机获取“IP 地址”的最佳方式通常不是查询本地计算机,而是询问您的程序正在与之通信的计算机它认为您计算机的 IP 地址是什么.例如如果您正在编写客户端程序,请向服务器发送一条消息,要求服务器将您的请求来自的 IP 地址作为数据发送回。这样,您将知道相关 IP 地址是什么,给定您正在与之通信的计算机的上下文。

也就是说,该技巧可能不适用于某些目的(例如,当您不与特定计算机通信时),因此有时您只需要收集与您的计算机关联的所有 IP 地址的列表。在 Unix/Mac (AFAIK) 下做到这一点的最佳方法是调用 getifaddrs() 并迭代结果。在 Windows 下,尝试 GetAdaptersAddresses() 以获得类似的功能。有关两者的用法示例,请参阅 this file 中的 GetNetworkInterfaceInfos() 函数。

【讨论】:

  • 使用网络查询而不是本地系统查询的逻辑似乎是合理的。我可以务实地开始做这件事的一种跨平台方式是什么?我使用的是 C++,主要是 Qt,但如果需要,我可以使用 STL (C++14)。
  • 服务器可以在其套接字上调用 getpeername() 以获取客户端的 IP 地址,然后将该数据发送回客户端(通过 TCP 连接)。
  • @Jeremy。恕我直言,查询服务器以获取本地 IP 解决方案并不是一个优雅的解决方案。我建议为连接到网关的 IP 枚举本地 IP。 *诚然,偶尔(例如在公司网络中)可能会有不止一个网关。
【解决方案2】:

所有基于 gethostbyname 的方法的问题是您不会获得分配给特定机器的所有 IP 地址。服务器通常有不止一个适配器。

这是一个如何遍历主机上所有 Ipv4 和 Ipv6 地址的示例:

void ListIpAddresses(IpAddresses& ipAddrs)
{
  IP_ADAPTER_ADDRESSES* adapter_addresses(NULL);
  IP_ADAPTER_ADDRESSES* adapter(NULL);

  // Start with a 16 KB buffer and resize if needed -
  // multiple attempts in case interfaces change while
  // we are in the middle of querying them.
  DWORD adapter_addresses_buffer_size = 16 * KB;
  for (int attempts = 0; attempts != 3; ++attempts)
  {
    adapter_addresses = (IP_ADAPTER_ADDRESSES*)malloc(adapter_addresses_buffer_size);
    assert(adapter_addresses);

    DWORD error = ::GetAdaptersAddresses(
      AF_UNSPEC, 
      GAA_FLAG_SKIP_ANYCAST | 
        GAA_FLAG_SKIP_MULTICAST | 
        GAA_FLAG_SKIP_DNS_SERVER |
        GAA_FLAG_SKIP_FRIENDLY_NAME, 
      NULL, 
      adapter_addresses,
      &adapter_addresses_buffer_size);

    if (ERROR_SUCCESS == error)
    {
      // We're done here, people!
      break;
    }
    else if (ERROR_BUFFER_OVERFLOW == error)
    {
      // Try again with the new size
      free(adapter_addresses);
      adapter_addresses = NULL;

      continue;
    }
    else
    {
      // Unexpected error code - log and throw
      free(adapter_addresses);
      adapter_addresses = NULL;

      // @todo
      LOG_AND_THROW_HERE();
    }
  }

  // Iterate through all of the adapters
  for (adapter = adapter_addresses; NULL != adapter; adapter = adapter->Next)
  {
    // Skip loopback adapters
    if (IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType)
    {
      continue;
    }

    // Parse all IPv4 and IPv6 addresses
    for (
      IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; 
      NULL != address;
      address = address->Next)
    {
      auto family = address->Address.lpSockaddr->sa_family;
      if (AF_INET == family)
      {
        // IPv4
        SOCKADDR_IN* ipv4 = reinterpret_cast<SOCKADDR_IN*>(address->Address.lpSockaddr);

        char str_buffer[INET_ADDRSTRLEN] = {0};
        inet_ntop(AF_INET, &(ipv4->sin_addr), str_buffer, INET_ADDRSTRLEN);
        ipAddrs.mIpv4.push_back(str_buffer);
      }
      else if (AF_INET6 == family)
      {
        // IPv6
        SOCKADDR_IN6* ipv6 = reinterpret_cast<SOCKADDR_IN6*>(address->Address.lpSockaddr);

        char str_buffer[INET6_ADDRSTRLEN] = {0};
        inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN);

        std::string ipv6_str(str_buffer);

        // Detect and skip non-external addresses
        bool is_link_local(false);
        bool is_special_use(false);

        if (0 == ipv6_str.find("fe"))
        {
          char c = ipv6_str[2];
          if (c == '8' || c == '9' || c == 'a' || c == 'b')
          {
            is_link_local = true;
          }
        }
        else if (0 == ipv6_str.find("2001:0:"))
        {
          is_special_use = true;
        }

        if (! (is_link_local || is_special_use))
        {
          ipAddrs.mIpv6.push_back(ipv6_str);
        }
      }
      else
      {
        // Skip all other types of addresses
        continue;
      }
    }
  }

  // Cleanup
  free(adapter_addresses);
  adapter_addresses = NULL;

  // Cheers!
}

【讨论】:

  • 这在 Windows 上运行良好。有谁知道,是否有在 Linux 上实现这一点的等效方法(或更一般:对于 POSIX 兼容的操作系统)?
  • 嘿!谢谢你,但是代码需要什么#includes才能工作?都找不到
【解决方案3】:

您可以使用 gethostname 后跟 gethostbyname 来获取本地接口内部 IP。

不过,此返回的 IP 可能与您的外部 IP 不同。要获取您的外部 IP,您必须与外部服务器通信,该服务器会告诉您您的外部 IP 是什么。因为外部 IP 不是你的,而是你的路由器。

//Example: b1 == 192, b2 == 168, b3 == 0, b4 == 100
struct IPv4
{
    unsigned char b1, b2, b3, b4;
};

bool getMyIP(IPv4 & myIP)
{
    char szBuffer[1024];

    #ifdef WIN32
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(2, 0);
    if(::WSAStartup(wVersionRequested, &wsaData) != 0)
        return false;
    #endif


    if(gethostname(szBuffer, sizeof(szBuffer)) == SOCKET_ERROR)
    {
      #ifdef WIN32
      WSACleanup();
      #endif
      return false;
    }

    struct hostent *host = gethostbyname(szBuffer);
    if(host == NULL)
    {
      #ifdef WIN32
      WSACleanup();
      #endif
      return false;
    }

    //Obtain the computer's IP
    myIP.b1 = ((struct in_addr *)(host->h_addr))->S_un.S_un_b.s_b1;
    myIP.b2 = ((struct in_addr *)(host->h_addr))->S_un.S_un_b.s_b2;
    myIP.b3 = ((struct in_addr *)(host->h_addr))->S_un.S_un_b.s_b3;
    myIP.b4 = ((struct in_addr *)(host->h_addr))->S_un.S_un_b.s_b4;

    #ifdef WIN32
    WSACleanup();
    #endif
    return true;
}

您也可以始终只使用代表本地计算机的 127.0.0.1。

Windows 中的子网掩码:

您可以通过查询此注册表项的子键来获取子网掩码(以及网关和其他信息):

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces

查找注册表值 SubnetMask。

其他获取Windows界面信息的方法:

您还可以使用以下方法检索您要查找的信息: WSAIoctl 使用此选项:SIO_GET_INTERFACE_LIST

【讨论】:

【解决方案4】:

在标准 C++ 中无法做到这一点。

我发布这个是因为它是唯一正确的答案。您的问题询问如何在 C++ 中执行此操作。好吧,你不能在 C++ 中做到这一点。您可以在 Windows、POSIX、Linux、Android 中执行此操作,但所有这些都是特定于操作系统的解决方案,而不是语言标准的一部分。

标准 C++根本没有网络层

我假设您有这样的错误假设,即 C++ 标准定义了与其他语言标准 Java 相同的功能范围。虽然 Java 可能在语言自己的标准库中内置了网络(甚至是 GUI 框架),但 C++ 没有。

虽然有 C++ 程序可以使用的第三方 API 和库,但这与说您可以在 C++ 中使用完全不同。

这里有一个例子来说明我的意思。您可以在 C++ 中打开一个文件,因为它有一个 fstream 类作为其标准库的一部分。这与使用CreateFile() 不同,CreateFile() 是特定于 Windows 的函数,仅适用于 WINAPI。

【讨论】:

    【解决方案5】:

    Winsock 特定:

    // Init WinSock
    WSADATA wsa_Data;
    int wsa_ReturnCode = WSAStartup(0x101,&wsa_Data);
    
    // Get the local hostname
    char szHostName[255];
    gethostname(szHostName, 255);
    struct hostent *host_entry;
    host_entry=gethostbyname(szHostName);
    char * szLocalIP;
    szLocalIP = inet_ntoa (*(struct in_addr *)*host_entry->h_addr_list);
    WSACleanup();
    

    【讨论】:

    • 不适用于 64 位操作系统:hostent 包含 baadfood 指针 :-)
    • gethostbyname 现在已弃用。请改用getaddrinfo()GetAddrInfoW()
    【解决方案6】:

    另外,请注意“本地 IP”可能不是一个特别独特的东西。如果您在多个物理网络上(例如有线+无线+蓝牙,或者有很多以太网卡的服务器等),或者设置了 TAP/TUN 接口,您的机器可以轻松拥有一整套接口。

    【讨论】:

      【解决方案7】:

      【讨论】:

      • 链接已失效...但这并不奇怪,因为距离原帖已经过去五年了!
      • 好电话,成功了!谢谢。看这里...web.archive.org/web/20100708151539/http://…
      • @patrickvacek 不幸的是,新链接还使用了已弃用的 gethostbyname()。必须使用 getaddrinfo() 或 GetAddrInfoW() 但可以返回多个地址。
      【解决方案8】:

      来自教程: 如果你使用winsock,这里有一个方法:http://tangentsoft.net/wskfaq/examples/ipaddr.html

      至于问题的子网部分;没有平台无关的方法来检索子网掩码,因为 POSIX 套接字 API(所有现代操作系统都实现)没有指定这一点。因此,您必须使用您正在使用的平台上可用的任何方法。

      【讨论】:

        【解决方案9】:

        我可以在 VS2013 下使用 DNS 服务,使用以下代码:

        #include <Windns.h>
        
        WSADATA wsa_Data;
        
        int wsa_ReturnCode = WSAStartup(0x101, &wsa_Data);
        
        gethostname(hostName, 256);
        PDNS_RECORD pDnsRecord;
        
        DNS_STATUS statsus = DnsQuery(hostName, DNS_TYPE_A, DNS_QUERY_STANDARD, NULL, &pDnsRecord, NULL);
        IN_ADDR ipaddr;
        ipaddr.S_un.S_addr = (pDnsRecord->Data.A.IpAddress);
        printf("The IP address of the host %s is %s \n", hostName, inet_ntoa(ipaddr));
        
        DnsRecordListFree(&pDnsRecord, DnsFreeRecordList);
        

        我不得不在链接器选项中添加 Dnsapi.lib 作为成瘾依赖项。

        参考here

        【讨论】:

        • 真的很不错。但是一个小的修正......在 DnsRecordListFree 函数中应该使用 pDnsRecord 代替 &pDnsRecord 因为 pDnsRecord 本身是一个指针;所以以后会发生内存泄漏。另一件事, intet_ntoa() 已被弃用,因此我使用 InetNtop() 将所需的 IP 地址存储在一个字符串中,该字符串是指向缓冲区的指针。现在,它工作正常,没有内存泄漏
        【解决方案10】:

        我建议我的代码。

        DllExport void get_local_ips(boost::container::vector<wstring>& ips)
        {
           IP_ADAPTER_ADDRESSES*       adapters  = NULL;
           IP_ADAPTER_ADDRESSES*       adapter       = NULL;
           IP_ADAPTER_UNICAST_ADDRESS* adr           = NULL;
           ULONG                       adapter_size = 0;
           ULONG                       err           = 0;
           SOCKADDR_IN*                sockaddr  = NULL;
        
           err = ::GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, NULL, &adapter_size);
           adapters = (IP_ADAPTER_ADDRESSES*)malloc(adapter_size);
           err = ::GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, adapters, &adapter_size);
        
           for (adapter = adapters; NULL != adapter; adapter = adapter->Next)
           {
               if (adapter->IfType     == IF_TYPE_SOFTWARE_LOOPBACK) continue; // Skip Loopback
               if (adapter->OperStatus != IfOperStatusUp) continue;            // Live connection only  
        
               for (adr = adapter->FirstUnicastAddress;adr != NULL; adr = adr->Next)
               {
                   sockaddr = (SOCKADDR_IN*)(adr->Address.lpSockaddr);
                   char    ipstr [INET6_ADDRSTRLEN] = { 0 };
                   wchar_t ipwstr[INET6_ADDRSTRLEN] = { 0 };
                   inet_ntop(AF_INET, &(sockaddr->sin_addr), ipstr, INET_ADDRSTRLEN);
                   mbstowcs(ipwstr, ipstr, INET6_ADDRSTRLEN);
                   wstring wstr(ipwstr);
                   if (wstr != "0.0.0.0") ips.push_back(wstr);                      
               }
           }
        
           free(adapters);
           adapters = NULL; }
        

        【讨论】:

          【解决方案11】:

          this answer 的修改版本。
          添加了头文件和库。

          它也基于这些页面:
          GetAdaptersAddresses
          IP_ADAPTER_ADDRESSES_LH
          IP_ADAPTER_UNICAST_ADDRESS_LH

          简而言之,要获取 IPv4 地址,请调用 GetAdaptersAddresses() 以获取适配器,然后遍历以 FirstUnicastAddress 开头的 IP_ADAPTER_UNICAST_ADDRESS 结构并获取 Address 字段,然后将其转换为可读格式inet_ntop()

          以以下格式打印信息:

          [ADAPTER]: Realtek PCIe
          [NAME]:    Ethernet 3
          [IP]:      123.123.123.123
          

          可以编译:

          cl test.cpp
          

          或者,如果您需要在命令行中添加库依赖项:

          cl test.cpp Iphlpapi.lib ws2_32.lib
          
          #include <winsock2.h>
          #include <iphlpapi.h>
          #include <stdio.h>
          #include <ws2tcpip.h>
          
          // Link with Iphlpapi.lib and ws2_32.lib
          #pragma comment(lib, "Iphlpapi.lib")
          #pragma comment(lib, "ws2_32.lib")
          
          void ListIpAddresses() {
            IP_ADAPTER_ADDRESSES* adapter_addresses(NULL);
            IP_ADAPTER_ADDRESSES* adapter(NULL);
            
            DWORD adapter_addresses_buffer_size = 16 * 1024;
            
            // Get adapter addresses
            for (int attempts = 0; attempts != 3; ++attempts) {
              adapter_addresses = (IP_ADAPTER_ADDRESSES*) malloc(adapter_addresses_buffer_size);
          
              DWORD error = ::GetAdaptersAddresses(AF_UNSPEC, 
                GAA_FLAG_SKIP_ANYCAST | 
                  GAA_FLAG_SKIP_MULTICAST | 
                  GAA_FLAG_SKIP_DNS_SERVER | 
                  GAA_FLAG_SKIP_FRIENDLY_NAME,
                NULL, 
                adapter_addresses,
                &adapter_addresses_buffer_size);
              
              if (ERROR_SUCCESS == error) {
                break;
              }
              else if (ERROR_BUFFER_OVERFLOW == error) {
                // Try again with the new size
                free(adapter_addresses);
                adapter_addresses = NULL;
                continue;
              }
              else {
                // Unexpected error code - log and throw
                free(adapter_addresses);
                adapter_addresses = NULL;
                return;
              }
            }
          
            // Iterate through all of the adapters
            for (adapter = adapter_addresses; NULL != adapter; adapter = adapter->Next) {
              // Skip loopback adapters
              if (IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) continue;
              
              printf("[ADAPTER]: %S\n", adapter->Description);
              printf("[NAME]:    %S\n", adapter->FriendlyName);
          
              // Parse all IPv4 addresses
              for (IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; NULL != address; address = address->Next) {
                auto family = address->Address.lpSockaddr->sa_family;
                if (AF_INET == family) {
                  SOCKADDR_IN* ipv4 = reinterpret_cast<SOCKADDR_IN*>(address->Address.lpSockaddr);
                  char str_buffer[16] = {0};
                  inet_ntop(AF_INET, &(ipv4->sin_addr), str_buffer, 16);
          
                  printf("[IP]:      %s\n", str_buffer);
                }
              }
              printf("\n");
            }
          
            free(adapter_addresses);
            adapter_addresses = NULL;
          }
          
          int main() {
            ListIpAddresses();
            return 0;
          }
          

          【讨论】:

            【解决方案12】:

            你不能直接发送到INADDR_BROADCAST吗?诚然,这将在所有接口上发送 - 但这很少有问题。

            否则,ioctl 和 SIOCGIFBRDADDR 应该在 *nix 上为您提供地址,在 win32 上为您提供WSAioctl and SIO_GET_BROADCAST_ADDRESS

            【讨论】:

              【解决方案13】:

              在 DEV C++ 中,我在 WIN32 下使用纯 C,并带有这段给定的代码:

              case IDC_IP:
              
                           gethostname(szHostName, 255);
                           host_entry=gethostbyname(szHostName);
                           szLocalIP = inet_ntoa (*(struct in_addr *)*host_entry->h_addr_list);
                           //WSACleanup(); 
                           writeInTextBox("\n");
                           writeInTextBox("IP: "); 
                           writeInTextBox(szLocalIP);
                           break;
              

              当我单击“显示 ip”按钮时,它可以工作。但是第二次,程序退出(没有警告或错误)。当我这样做时:

              //WSACleanup(); 
              

              即使以最快的速度多次单击同一个按钮,程序也不会退出。 所以 WSACleanup() 可能不适用于 Dev-C++..

              【讨论】:

              • 不,问题不在于你的编译器。当您调用WSACleanup 时,程序“终止使用 Winsock 2 DLL (Ws2_32.dll)”。所以一旦你调用它,你可能不再进行套接字函数调用。在程序结束之前不要调用 WSACleanup...
              • gethostbyname 可能无法在 64 位操作系统上运行:hostent 包含 baadfood 指针 :-)
              猜你喜欢
              • 2011-06-14
              • 2012-10-28
              • 2019-04-26
              • 1970-01-01
              • 2012-09-01
              • 1970-01-01
              • 2010-09-14
              • 1970-01-01
              相关资源
              最近更新 更多