使用 Boost.Asio 获取本地 IP 地址

Posted

技术标签:

【中文标题】使用 Boost.Asio 获取本地 IP 地址【英文标题】:Get Local IP-Address using Boost.Asio 【发布时间】:2011-02-10 02:03:29 【问题描述】:

我目前正在寻找一种可移植的方式来获取本地 IP 地址。因为无论如何我都在使用 Boost,所以我认为使用 Boost.Asio 来完成这项任务是个好主意。

网上有几个例子可以解决问题。例子:

Official Boost.Asio Documentation

Some Asian Page

我尝试了这两个代码,只是稍作修改。 Boost.Doc 上的代码已更改为不解析“www.boost.org”,而是解析“localhost”或我的主机名。为了获取主机名,我使用了 boost::asio::ip::host_name() 或直接将其输入为字符串。

此外,我编写了自己的代码,该代码融合了上述示例以及我从 Boost 文档和其他示例中收集的(少量)知识。

所有来源都有效,但它们确实返回了以下 IP: 127.0.1.1(这不是错字,最后是 .1.1) 我使用 GCC 4.4.1 在 Ubuntu 9.10 上运行并编译了代码

一位同事在他的机器上尝试了相同的代码并得到了 127.0.0.2(也不是错字...) 他使用 GCC 4.4.1 在 Suse 11.0 上编译和运行(我不是 100% 确定)

我不知道是否可以更改 localhost (127.0.0.1),但我知道我或我的同事都没有这样做。 ifconfig 说环回使用 127.0.0.1。 ifconfig 还找到了我正在搜索的公共 IP(在我的例子中是 141.200.182.30,子网是 255.255.0.0)

那么这是一个 Linux 问题吗?代码不像我想象的那么可移植?我是否必须更改其他内容,或者 Boost.Asio 根本无法解决我的问题?

我知道 *** 和其他页面上有很多关于类似主题的问题,但我找不到对我有用的信息。如果你有有用的链接,如果你能指出我会很好。

PS: 这是我从 Boost.Doc 中使用的修改后的代码:

#include <boost/asio.hpp>
using boost::asio::ip::tcp;    

boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(boost::asio::ip::host_name(), "");
tcp::resolver::iterator iter = resolver.resolve(query);
tcp::resolver::iterator end; // End marker.
while (iter != end)

    tcp::endpoint ep = *iter++;
    std::cout << ep << std::endl;

【问题讨论】:

AFAIK,ASIO 没有提供一种方法来枚举您机器的接口(Linux 上的 SIOCGIFCONF ioctl 或 Windows 上的 GetAdaptersAddresses),这似乎就是您所追求的。您显示的代码使用您的机器的主机名查询 DNS,这不是一回事,并且由于它更多地依赖于您的网络配置(特别是,DNS 必须“知道”您的机器的名称),因此不太健壮。跨度> asio::ip::address_v4::loopback() 是否满足您的需求? 【参考方案1】:

对我来说,基于解析的方法在各种极端情况下总是被证明是不可靠的。

操作系统提供API如

getifaddrs 在 Linux 上 (https://man7.org/linux/man-pages/man3/getifaddrs.3.html) 在 macOS 和 BSD 上相同 (https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/getifaddrs.3.html) GetAdaptersAddresses 在 Win32 上 (https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses)。

请注意,Boost.ASIO 绝不包含对这些函数的调用,因此您只能使用本地域解析方法。

如果您正在寻找调用上述操作系统功能的跨平台解决方案,Qt 提供了它:

for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces())
  for (const QNetworkAddressEntry& entry : iface.addressEntries())
    qDebug() << entry.ip();

【讨论】:

【参考方案2】:

假设你有一张网卡/一个本地ip地址:

#include <boost/asio.hpp>
namespace ip = boost::asio::ip;

std::string getAddress()

    boost::asio::io_service ioservice;
    ip::tcp::resolver resolver(ioService);

    return resolver.resolve(ip::host_name(), "")->endpoint().address().to_string();

【讨论】:

【参考方案3】:

跨平台,但只因#ifdef _WIN32 … #else

boost::asio::ip::address_v6 sinaddr_to_asio(sockaddr_in6 *addr) 
    boost::asio::ip::address_v6::bytes_type buf;
    memcpy(buf.data(), addr->sin6_addr.s6_addr, sizeof(addr->sin6_addr));
    return boost::asio::ip::make_address_v6(buf, addr->sin6_scope_id);


#if defined(_WIN32)
#undef UNICODE
#include <winsock2.h>
// Headers that need to be included after winsock2.h:
#include <iphlpapi.h>
#include <ws2ipdef.h>

typedef IP_ADAPTER_UNICAST_ADDRESS_LH Addr;
typedef IP_ADAPTER_ADDRESSES *AddrList;

std::vector<boost::asio::ip::address> get_local_interfaces() 
    // It's a windows machine, we assume it has 512KB free memory
    DWORD outBufLen = 1 << 19;
    AddrList ifaddrs = (AddrList) new char[outBufLen];

    std::vector<boost::asio::ip::address> res;

    ULONG err = GetAdaptersAddresses(AF_UNSPEC,
        GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, ifaddrs,
        &outBufLen);

    if (err == NO_ERROR) 
        for (AddrList addr = ifaddrs; addr != 0; addr = addr->Next) 
            if (addr->OperStatus != IfOperStatusUp) continue;
            // if (addr->NoMulticast) continue;

            // Find the first IPv4 address
            if (addr->Ipv4Enabled) 
                for (Addr *uaddr = addr->FirstUnicastAddress; uaddr != 0; uaddr = uaddr->Next) 
                    if (uaddr->Address.lpSockaddr->sa_family != AF_INET) continue;
                    res.push_back(boost::asio::ip::make_address_v4(ntohl(reinterpret_cast<sockaddr_in *>(addr->ifa_addr)->sin_addr.s_addr)));
                
            

            if (addr->Ipv6Enabled) 
                for (Addr *uaddr = addr->FirstUnicastAddress; uaddr != 0; uaddr = uaddr->Next) 
                    if (uaddr->Address.lpSockaddr->sa_family != AF_INET6) continue;
                    res.push_back(sinaddr_to_asio(reinterpret_cast<sockaddr_in6 *>(addr->ifa_addr)));
                
            
        
     else 

    
    delete[]((char *)ifaddrs);
    return res;

#elif defined(__APPLE__) || defined(__linux__)
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <sys/types.h>

std::vector<boost::asio::ip::address> get_local_interfaces() 
    std::vector<boost::asio::ip::address> res;
    ifaddrs *ifs;
    if (getifaddrs(&ifs)) 
        return res;
    
    for (auto addr = ifs; addr != nullptr; addr = addr->ifa_next) 
        // No address? Skip.
        if (addr->ifa_addr == nullptr) continue;

        // Interface isn't active? Skip.
        if (!(addr->ifa_flags & IFF_UP)) continue;

        if(addr->ifa_addr->sa_family == AF_INET) 
            res.push_back(boost::asio::ip::make_address_v4(ntohl(
                reinterpret_cast<sockaddr_in *>(addr->ifa_addr)->sin_addr.s_addr)));
         else if(addr->ifa_addr->sa_family == AF_INET6) 
            res.push_back(sinaddr_to_asio(reinterpret_cast<sockaddr_in6 *>(addr->ifa_addr)));
         else continue;
    
    freeifaddrs(ifs);
    return res;

#else
#error "..."
#endif

【讨论】:

ifa_addr 在 Windows 实现中不存在。用 addr->FirstUnicastAddress->Address.lpSockaddr 替换 addr->ifa_addr 似乎可行。【参考方案4】:

您可以使用您发布的代码找到“您的”地址。但是......它变得复杂。可能有多个 NIC,可能有 LAN 和 WAN 地址、有线和无线、环回...

我发现让用户提供要绑定的 IP 作为命令行参数会更好。是的,这是一个便携式解决方案! :-)

【讨论】:

【参考方案5】:

这是我从 python 网络编程 (google) 中学到的一个技巧,用于计算我机器的 IP 地址。这仅在您有互联网连接并且可以连接到 google.com 并且确实给了我家用计算机的 192.168.x.x 私人地址时才有效。

try 
    boost::asio::io_service netService;
    udp::resolver   resolver(netService);
    udp::resolver::query query(udp::v4(), "google.com", "");
    udp::resolver::iterator endpoints = resolver.resolve(query);
    udp::endpoint ep = *endpoints;
    udp::socket socket(netService);
    socket.connect(ep);
    boost::asio::ip::address addr = socket.local_endpoint().address();
    std::cout << "My IP according to google is: " << addr.to_string() << std::endl;
  catch (std::exception& e)
    std::cerr << "Could not deal with socket. Exception: " << e.what() << std::endl;

 

【讨论】:

根据谷歌,这不是你的IP。它是你机器上配置接口的IP。 就是我们需要的本地ip地址。正如@MONsDaR 所说,ifconfig/ipconfig 返回的那个。当我有其他机器要连接时,它适用于我的特殊情况。 很确定这是您机器上用于连接谷歌的端点的 IP(并且可能是您的主要互联网端点)【参考方案6】:

如果您编辑您的 /etc/hosts 文件(这仅适用于 *nix,也可能适用于 Windows……我不确定)您可以更正此问题。

在 hosts 文件中,你会发现类似这样的内容:(这是 Ubuntu,注意 1.1)

127.0.0.1 本地主机 127.0.1.1 yourPcName.yourNetwork.tld

如果您将此文件更改为

127.0.0.1 本地主机 127.0.1.1 yourPcName.yourNetwork.tld your.real.ip.here yourPcName

那么主机名应该正确解析。

测试正确分辨率的一种方法是使用“hostname -i”命令,它会在您更改主机之前错误地打印您的 IP 地址,然后再正确打印。

当然,这对于动态 IP 来说是个糟糕的解决方案……嗯。

【讨论】:

感谢您的提示。显然 Boost.Asio 只是在使用 *nix 系统时读取这些值。它适用于 Ubuntu 9.10、10.04 和 Suse 11.2。

以上是关于使用 Boost.Asio 获取本地 IP 地址的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 boost::asio::ip::tcp::socket 的 IP 地址?

Boost asio socket:如何获取IP,连接的端口地址?

如何使用 boost::asio 将 URL 转换为 IP 地址?

使用 boost::asio 获取 UDP 套接字远程地址

使用 boost asio udp 套接字时如何设置本地端点

如何使用 boost asio 进行 IP 地址操作(屏蔽、or'ing 等)?