简体   繁体   English

如何获取本地计算机的 IP 地址?

[英]How can I get the IP Address of a local computer?

In C++, what's the easiest way to get the local computer's IP address and su.net mask?在C++中,最简单的获取本地计算机的IP地址和su.net掩码的方法是什么?

I want to be able to detect the local machine's IP address in my local.network.我希望能够在我的 local.network 中检测到本地机器的 IP 地址。 In my particular case, I have a.network with a su.net mask of 255.255.255.0 and my computer's IP address is 192.168.0.5.在我的特殊情况下,我有一个 su.net 掩码为 255.255.255.0 的.network,我的计算机的 IP 地址是 192.168.0.5。 I need to get these had two values programmatically in order to send a broadcast message to my.network (in the form 192.168.0.255, for my particular case)我需要以编程方式获得这两个值,以便向 my.network 发送广播消息(对于我的特殊情况,以 192.168.0.255 的形式)

Edit: Many answers were not giving the results I expected because I had two different.network IP's.编辑:许多答案没有给出我预期的结果,因为我有两个不同的网络 IP。 Torial 's code did the trick (it gave me both IP addresses). Torial的代码成功了(它给了我两个 IP 地址)。

Edit 2: Thanks to Brian R. Bondy for the info about the su.net mask.编辑 2:感谢Brian R。Bondy提供有关 su.net 掩码的信息。

The question is trickier than it appears, because in many cases there isn't "an IP address for the local computer" so much as a number of different IP addresses.这个问题比看起来更棘手,因为在许多情况下,“本地计算机的 IP 地址”并不像许多不同的 IP 地址那样多。 For example, the Mac I'm typing on right now (which is a pretty basic, standard Mac setup) has the following IP addresses associated with it:例如,我现在正在使用的 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

... and it's not just a matter of figuring out which of the above is "the real IP address", either... they are all "real" and useful; ……这不仅仅是弄清楚上面哪个是“真实IP地址”的问题,或者……它们都是“真实的”和有用的; some more useful than others depending on what you are going to use the addresses for.一些比其他更有用取决于您将使用地址的目的。

In my experience often the best way to get "an IP address" for your local computer is not to query the local computer at all, but rather to ask the computer your program is talking to what it sees your computer's IP address as.根据我的经验,获得本地计算机“IP 地址”的最佳方法通常根本不是查询本地计算机,而是询问您的程序正在与之通信的计算机,它认为您的计算机的 IP 地址是什么。 eg if you are writing a client program, send a message to the server asking the server to send back as data the IP address that your request came from.例如,如果您正在编写客户端程序,请向服务器发送一条消息,要求服务器将您的请求所来自的 IP 地址作为数据发回。 That way you will know what the relevant IP address is, given the context of the computer you are communicating with.这样您就可以知道相关的IP 地址是什么,考虑到您正在与之通信的计算机的上下文。

That said, that trick may not be appropriate for some purposes (eg when you're not communicating with a particular computer) so sometimes you just need to gather the list of all the IP addresses associated with your machine.也就是说,该技巧可能不适用于某些目的(例如,当您不与特定计算机通信时),因此有时您只需要收集与您的机器相关联的所有 IP 地址的列表。 The best way to do that under Unix/Mac (AFAIK) is by calling getifaddrs() and iterating over the results.在 Unix/Mac (AFAIK) 下执行此操作的最佳方法是调用 getifaddrs() 并迭代结果。 Under Windows, try GetAdaptersAddresses() to get similar functionality.在 Windows 下,尝试 GetAdaptersAddresses() 以获得类似的功能。 For example usages of both, see the GetNetworkInterfaceInfos() function in this file .有关两者的用法示例,请参阅此文件中的 GetNetworkInterfaceInfos() 函数。

The problem with all the approaches based on gethostbyname is that you will not get all IP addresses assigned to a particular machine.所有基于 gethostbyname 的方法的问题在于,您不会获得分配给特定机器的所有 IP 地址。 Servers usually have more than one adapter.服务器通常有多个适配器。

Here is an example of how you can iterate through all Ipv4 and Ipv6 addresses on the host machine:下面是一个示例,说明如何遍历主机上的所有 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!
}

You can use gethostname followed by gethostbyname to get your local interface internal IP.您可以使用 gethostname 后跟 gethostbyname 来获取本地接口内部 IP。

This returned IP may be different from your external IP though.不过,这个返回的 IP 可能与您的外部 IP 不同。 To get your external IP you would have to communicate with an external server that will tell you what your external IP is.要获得外部 IP,您必须与外部服务器通信,该服务器会告诉您外部 IP 是什么。 Because the external IP is not yours but it is your routers.因为外部 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;
}

You can also always just use 127.0.0.1 which represents the local machine always.您也可以始终只使用 127.0.0.1,它始终代表本地机器。

Subnet mask in Windows: Windows 中的子网掩码:

You can get the subnet mask (and gateway and other info) by querying subkeys of this registry entry:您可以通过查询此注册表项的子项来获取子网掩码(以及网关和其他信息):

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

Look for the registry value SubnetMask.查找注册表值 SubnetMask。

Other methods to get interface information in Windows:在 Windows 中获取接口信息的其他方法:

You could also retrieve the information you're looking for by using: WSAIoctl with this option: SIO_GET_INTERFACE_LIST您还可以使用以下选项检索您要查找的信息: WSAIoctl :SIO_GET_INTERFACE_LIST

You cannot do that in Standard C++.你不能在标准 C++ 中做到这一点。

I'm posting this because it is the only correct answer.我发布这个是因为它是唯一正确的答案。 Your question asks how to do it in C++.您的问题询问如何在 C++ 中做到这一点。 Well, you can't do it in C++.好吧,你不能在 C++ 中做到这一点。 You can do it in Windows, POSIX, Linux, Android, but all those are OS-specific solutions and not part of the language standard.您可以在 Windows、POSIX、Linux、Android 中执行此操作,但所有这些都是特定于操作系统的解决方案,而不是语言标准的一部分。

Standard C++ does not have a networking layer at all.标准 C++根本没有网络层

I assume you have this wrong assumption that C++ Standard defines the same scope of features as other language standards, Java.我假设您有一个错误的假设,即 C++ 标准定义了与其他语言标准 Java 相同的特性范围。 While Java might have built-in networking (and even a GUI framework) in the language's own standard library, C++ does not.虽然 Java 可能在语言自己的标准库中内置了网络(甚至是 GUI 框架),但 C++ 没有。

While there are third-party APIs and libraries which can be used by a C++ program, this is in no way the same as saying that you can do it in C++.尽管存在可供 C++ 程序使用的第三方 API 和库,但这与说您可以在 C++ 中执行完全不同。

Here is an example to clarify what I mean.这是一个例子来阐明我的意思。 You can open a file in C++ because it has an fstream class as part of its standard library.您可以在 C++ 中打开文件,因为它有一个fstream类作为其标准库的一部分。 This is not the same thing as using CreateFile() , which is a Windows-specific function and available only for WINAPI.这与使用CreateFile() ,后者是 Windows 特定的函数,仅适用于 WINAPI。

Winsock specific:温索克具体:

// 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();

Also, note that "the local IP" might not be a particularly unique thing.另请注意,“本地 IP”可能不是特别独特的东西。 If you are on several physical networks (wired+wireless+bluetooth, for example, or a server with lots of Ethernet cards, etc.), or have TAP/TUN interfaces setup, your machine can easily have a whole host of interfaces.如果您在多个物理网络上(例如,有线+无线+蓝牙,或具有大量以太网卡的服务器等),或者设置了 TAP/TUN 接口,您的机器可以轻松拥有大量接口。

How to Obtain the IP Address of the Local Machine on the Network seems to describe the solution quite well... 如何获取网络上本地机器的IP地址似乎很好地描述了解决方案......

from torial: If you use winsock, here's a way: http://tangentsoft.net/wskfaq/examples/ipaddr.html来自 torial:如果你使用 winsock,这里有一个方法: http : //tangentsoft.net/wskfaq/examples/ipaddr.html

As for the subnet portion of the question;至于问题的子网部分; there is not platform agnostic way to retrieve the subnet mask as the POSIX socket API (which all modern operating systems implement) does not specify this.没有平台无关的方式来检索子网掩码,因为 POSIX 套接字 API(所有现代操作系统都实现)没有指定这一点。 So you will have to use whatever method is available on the platform you are using.因此,您必须使用您所使用的平台上可用的任何方法。

I was able to do it using DNS service under VS2013 with the following code:我能够在 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);

I had to add Dnsapi.lib as addictional dependency in linker option.我不得不在链接器选项中添加 Dnsapi.lib 作为依赖。

Reference here .参考这里

I suggest my code.我建议我的代码。

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; }

A modified version of this answer .此答案的修改版本。
Added headers and libs.添加了标头和库。

It's also based on these pages:它也基于这些页面:
GetAdaptersAddresses 获取适配器地址
IP_ADAPTER_ADDRESSES_LH IP_ADAPTER_ADDRESSES_LH
IP_ADAPTER_UNICAST_ADDRESS_LH IP_ADAPTER_UNICAST_ADDRESS_LH

In short, to get an IPv4 address, you call GetAdaptersAddresses() to get the adapters, then run through the IP_ADAPTER_UNICAST_ADDRESS structures starting with FirstUnicastAddress and get the Address field to then convert it to a readable format with inet_ntop() .总之,要获得一个IPv4地址,调用GetAdaptersAddresses()来获取适配器,然后通过运行IP_ADAPTER_UNICAST_ADDRESS结构开始FirstUnicastAddress并获得Address字段,然后将它与转换为可读格式inet_ntop()

Prints info in the format:以以下格式打印信息:

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

Can be compiled with:可以编译为:

cl test.cpp

or, if you need to add libs dependencies in the command line:或者,如果您需要在命令行中添加 libs 依赖项:

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;
}

Can't you just send to INADDR_BROADCAST ?你不能直接发送到INADDR_BROADCAST吗? Admittedly, that'll send on all interfaces - but that's rarely a problem.诚然,这将在所有接口上发送 - 但这很少成为问题。

Otherwise, ioctl and SIOCGIFBRDADDR should get you the address on *nix, and WSAioctl and SIO_GET_BROADCAST_ADDRESS on win32.否则,ioctl 和 SIOCGIFBRDADDR 应该为您提供 *nix 上的地址,以及 win32 上的WSAioctl 和 SIO_GET_BROADCAST_ADDRESS

In DEV C++, I used pure C with WIN32, with this given piece of code:在 DEV C++ 中,我将纯 C 与 WIN32 一起使用,并使用以下给定的代码:

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;

When I click the button 'show ip', it works.当我单击“显示 ip”按钮时,它起作用了。 But on the second time, the program quits (without warning or error).但是第二次,程序退出(没有警告或错误)。 When I do:当我做:

//WSACleanup(); 

The program does not quit, even clicking the same button multiple times with fastest speed.程序不会退出,甚至以最快的速度多次单击同一个按钮。 So WSACleanup() may not work well with Dev-C++..所以 WSACleanup() 可能不适用于 Dev-C++ ..

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM