【问题标题】:Get the local ip address with unix sockets使用 unix 套接字获取本地 IP 地址
【发布时间】:2014-11-11 12:20:36
【问题描述】:

我正在编写一个简单的 UDP 客户端服务器聊天,以便更熟悉网络编程。现在让用户更容易看到服务器的 IP 地址是什么(以便用户可以将其键入客户端进行连接)。这就是为什么我希望服务器显示自己的本地 IP 地址。

这是我的代码:(处理多个 IP 的编辑代码)

// get the hostname of the server
char hostname[128];
if (gethostname(hostname, sizeof(hostname)) == -1) {
    std::cout << "Could not get the hostname of the server. Error: " << std::strerror(errno) << std::endl;
    return 1;
}

struct addrinfo hints, *serverInfo, *p;

std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // use AF_INET (IPv4) or AF_INET6 (IPv6) to force version
hints.ai_socktype = SOCK_DGRAM;

// try to get information about our hostname
int rv;
if ((rv = getaddrinfo(hostname, NULL, &hints, &serverInfo)) != 0) {
    std::cout << "Failed to get information about the host \"" << hostname << "\". Error: " << gai_strerror(rv) << std::endl;
    return 1;
}

std::cout << "IP addresses for " << hostname << ":" << std::endl;

// iterate over all the infos we got and try to extract the IP address
for(p = serverInfo; p != NULL; p = p->ai_next) {
    char serverIPAddress[INET6_ADDRSTRLEN];
    void *addr;
    std::string ipVersion;

    // get the pointer to the address itself,
    // different fields in IPv4 and IPv6:
    if (p->ai_family == AF_INET) { // IPv4
        struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
        addr = &(ipv4->sin_addr);
        ipVersion = "IPv4";
    } else { // IPv6
        struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
        addr = &(ipv6->sin6_addr);
        ipVersion = "IPv6";
    }

    // take the IP address of our hostname and convert it into a readable format
    inet_ntop(p->ai_family, addr, serverIPAddress, sizeof(serverIPAddress));
    std::cout << "  " << ipVersion << ": " << serverIPAddress << std::endl;
}

freeaddrinfo(serverInfo); // free the linked list

它可以工作,但它不显示正确的 IP 地址。每次我运行它时,都会显示一个不同的 IP,而且它不是正确的。正如我所说,我仍在学习网络编程,所以我不知道为什么会这样。有人能指出我正确的方向吗?

【问题讨论】:

    标签: c++ sockets unix network-programming


    【解决方案1】:

    您将无法像这样获得“全球”IP 地址,只能获得本地 IP 地址。为了让客户端连接到您,它需要从 Internet 可见的全局 IP 地址,即您的网关(路由器)的 IP。这是因为您的路由器将您计算机的 IP 地址转换为全球可见的 IP 地址并将其存储在表格中,以便传入连接可以正确路由到正确的子网络。这称为 NATing(详细了解 NAT here)。

    如果您希望客户端显式地连接到您,您需要向他们提供您的路由器的 IP 地址(为此使用命令行工具,具体取决于您的操作系统)并配置端口转发,以便路由器将自动将到达(IP_router,port_router)的数据包转发到(IP_host,port_host)。这是在路由器的配置面板中完成的。根据您的路由器,这将在菜单中的某处找到。您可以通过输入 192.068.0.1192.168.0.0 之类的内容连接到路由器。

    【讨论】:

    • 感谢您的快速回答!但就像我在原始问题中所说的那样,我只想获取我的本地 IP 地址。聊天客户端真的很简单,只应该在本地网络中工作(比如我的大学或家里的网络)。
    • 好的。如果它只是本地的,这可以通过多种方式完成。您的客户端可以在网络上针对您的客户端的特定端口(例如 987654)广播一个 UDP 发现数据包,在此端口上的某些机器上侦听的服务器将使用发现数据包的源(IP,端口)建立 TCP 连接信息。另一种方法是简单地使用ipconfig 来查找您的 IP 地址,如果您不想经历上述所有操作。
    • 啊,现在我们在同一页上 :) 我基本上只是想要 ipconfig 所做的。所以当我打开一个服务器时它说:Welcome! This server is reachable under the IP: x.x.x.x 然后我可以转到另一台机器,打开一个客户端并输入 IP 地址。这应该是一个方便的功能,所以不必调用ipconfig每次我启动服务器时(例如,我的大学 PC 没有安装 ipconfig...)
    • 这通常是不可能的,因为您的机器可能有超过 1 个网络接口。要枚举您的网络接口,请使用 myk 上面发布的算法。然后您可以滚动浏览网络接口并选择您知道的网卡。
    • 嗯,我明白了。我想我会列出所有的网络接口。只是出于好奇,如果有多个接口,getaddrinfo 不应该返回一个列表吗?
    【解决方案2】:

    在 Windows 上,您可以使用 gethostbyname 并通过 hostent 枚举 h_addr_list... 在 Linux 上,您可以创建一个临时套接字并在 SIOCGIFCONF 上使用 ioctl 并通过接口枚举。

    【讨论】:

    • 您必须小心以这种方式使用gethostbyname()。有一些方法可以返回错误的结果。最好改用GetAdaptersInfo()GetAdaptersAddresses()。在 Linux 上,请改用 getifaddrs()SIOCGIFCONF 仅支持 IPv4,而getifaddrs() 同时支持 IPv4 和 IPv6。
    • 感谢您的评论。 gethostbyname 在哪些情况下会得到错误的结果?想知道是否值得我在那里查看代码...
    • 使用gethostname()gethostbyname() 可能并不总是只返回机器的IP 地址。从技术上讲,它们将返回本地主机名,然后返回该主机名解析到的地址。一台机器可能 (a) 被配置为它的名称不解析为 IP,或 (b) 被配置为它的名称解析为多个 IP,其中只有一个属于本地机器。使用直接枚举本地网络接口的特定于平台的 API,如 GetAdapters(Info|Addresses)()getifaddrs(),可以避免这个陷阱。
    【解决方案3】:

    我认为在您的代码末尾,您误用了数据结构 sockaddr_in。 行:

    inet_ntop(AF_INET, &(serverInfo->ai_addr), serverIPAddress, INET_ADDRSTRLEN);
    

    应替换为:

    struct sockaddr_in *ipv4 = (struct sockaddr_in *)serverInfo->ai_addr;
    void *addr = &(ipv4->sin_addr);
    inet_ntop(AF_INET, addr, serverIPAddress, INET_ADDRSTRLEN);
    

    【讨论】:

    • 确实!那讲得通。我调整了我的代码以正确处理该问题,并处理从getaddrinfo 返回的多个addrinfo。但我唯一得到的是 127.0.1.1(这不是我的环回),但不是我的本地 IP 地址。
    【解决方案4】:
    // Get a Vector of the IP addresses of this computer
    std::vector<std::string> getIPAddresses() {
    
        std::vector<std::string> IPAddresses;
        int TempAddress = 0;
        char* TempIPAddress = (char*) malloc(16);
    
        ifaddrs* ifap = NULL;   //Head of the interface address linked list
    
        if (getifaddrs(&ifap) == 0 && ifap != NULL) {
    
            //Get another pointer to move down the linked list
            ifaddrs* current = ifap;
    
            //Move down the linked list until we get to a NULL pointer
            while (current != NULL) {
    
                //Create a pointer of the correct type to work with
                const sockaddr_in* interfaceAddress = reinterpret_cast<const sockaddr_in*>(current->ifa_addr);
    
                if (current->ifa_addr != NULL) {
                    if (current->ifa_addr->sa_family == AF_INET) {
                        //printf("%s:", current->ifa_name);
                        if (interfaceAddress != NULL) {
                            TempAddress = ntohl(interfaceAddress->sin_addr.s_addr);
                            sprintf(TempIPAddress, "%d.%d.%d.%d", (TempAddress >> 24) & 0xFF, (TempAddress >> 16) & 0xFF,
                                   (TempAddress >> 8) & 0xFF, TempAddress & 0xFF);
                            //Don't include the lookback address
                            if (strcmp(TempIPAddress, "127.0.0.1") != 0) {
                                IPAddresses.push_back(std::string(TempIPAddress));
                            }
                            //printf("%s\n", TempIPAddress);
                        }
                    }
                }
    
                //Move to the next node in the linked-list
                current = current->ifa_next;
            }
    
            //Release the interface memory
            freeifaddrs(ifap);
            ifap = NULL;
        }
    
        return IPAddresses;
    
    }
    

    【讨论】:

      【解决方案5】:

      Soo,多亏了我在这里得到的建议,我改变了我的方法,而不是尝试将我的机器的主机名解析为 IP 地址,我现在只是列出所有网络接口及其 IP 地址。

      这是我的代码:

      // get the hostname of the server
      char hostname[128];
      if (gethostname(hostname, sizeof(hostname)) == -1) {
          std::cout << "Could not get the hostname of the server. Error: " << std::strerror(errno) << std::endl;
          return 1;
      }
      
      std::cout << "IP addresses for " << hostname << ":" << std::endl << std::endl;
      
      struct ifaddrs *networkInterfaceList, *p;
      
      getifaddrs (&networkInterfaceList); // get information about the network interfaces
      
      // iterate over all the network interfaces we got and try to extract their IP address
      for (p = networkInterfaceList; p != NULL; p = p->ifa_next) {
          char serverIPAddress[INET6_ADDRSTRLEN];
          void *addr;
          std::string ipVersion;
      
          // get the pointer to the address itself,
          // different fields in IPv4 and IPv6:
          if (p->ifa_addr->sa_family == AF_INET) { // IPv4
              struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ifa_addr;
              addr = &(ipv4->sin_addr);
              ipVersion = "IPv4";
          } else { // IPv6
              struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ifa_addr;
              addr = &(ipv6->sin6_addr);
              ipVersion = "IPv6";
          }
      
          // take the IP address of our hostname and convert it into a readable format
          inet_ntop(p->ifa_addr->sa_family, addr, serverIPAddress, sizeof(serverIPAddress));
      
          // sometime the IP address is empty so only print if there is one
          if (std::strlen(serverIPAddress) > 0)
              std::cout << "Interface: " << std::setw(6) << std::left << p->ifa_name << " " << ipVersion << ": " << serverIPAddress << std::endl;
      }
      
      freeifaddrs(networkInterfaceList); // free the linked list
      

      以防万一其他人正在尝试类似的事情。

      如果有人对这里感兴趣,这里是聊天库:https://github.com/Foaly/Chat

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-04-27
        • 2013-02-07
        • 2013-07-01
        • 2011-02-10
        • 2013-01-20
        • 2011-10-11
        • 1970-01-01
        • 2013-04-01
        相关资源
        最近更新 更多