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

31

我目前正在寻找一种获取本地IP地址的便携方式。由于我已经在使用Boost,所以我认为使用Boost.Asio完成这个任务是一个好主意。

网络上有几个例子可以完成这个任务。例如:

官方Boost.Asio文档

某些亚洲页面

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

此外,我编写了自己的代码,这是上述示例和我从Boost文档和其他示例中获得的(少量)知识的结合。

所有的源代码都起作用,但它们只返回以下IP:
127.0.1.1(这不是笔误,在末尾有.1.1)
我在Ubuntu 9.10上运行和编译了代码,并使用GCC 4.4.1

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

我不知道是否可以更改本地主机名(127.0.0.1),但我知道我和我的同事都没有这样做。ifconfig显示环回使用127.0.0.1。ifconfig还找到了我正在寻找的公共IP(在我的情况下为141.200.182.30,子网是255.255.0.0)。
所以这是一个Linux问题,代码并不像我想象的那样可移植吗?我需要改变其他东西还是Boost.Asio根本不能解决我的问题?
我知道Stackoverflow和其他页面上有很多类似主题的问题,但我找不到对我有用的信息。如果您有有用的链接,指向它将会很好。
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;
}

13
据我所知,ASIO没有提供一种列举机器接口的方法(在Linux上是SIOCGIFCONF ioctl或在Windows上是GetAdaptersAddresses),而这似乎是你想要的。你展示的代码查询了带有你机器主机名的DNS,但这不是同样的事情,而且更不够健壮,因为它更依赖于你网络的配置(特别是DNS必须“知道”你机器的名称)。 - Éric Malenfant
asio::ip::address_v4::loopback() 是否满足你的需求? - yanpas
6个回答

9

以下是我从Python网络编程(谷歌)学到的技巧,可以帮你查找计算机的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;

 }

6
根据谷歌的说法,那不是你的IP地址。它是你计算机上配置的接口的IP地址。 - unixman83
1
这是我们需要本地IP地址的原因。正如@MOnsDaR所说,可以通过ifconfig/ipconfig返回一个IP地址。 在我需要连接其他机器的特殊情况下,它能够正常工作。 - Fantastory
很确定这是连接到谷歌时在您的计算机上使用的端点的IP地址(很可能是您的主要互联网端点)。 - dwb

6
您可以使用您发布的代码找到“您”的地址。但是……它变得更加复杂了。可能存在多个NIC,可能存在LAN和WAN地址,有线和无线连接,回环连接...在我的台式机上,我有一个NIC,但在我的局域网中有两个不同DHCP服务器分配的IP地址...
我发现最好让用户作为命令行参数提供要绑定的IP地址。是的,这是一个便携解决方案! :-)

5
如果你编辑的是/etc/hosts文件(这只适用于*nix,可能也适用于Windows……我不确定),你可以解决这个问题。
在hosts文件中,你会发现类似以下内容:(这是Ubuntu,注意1.1)
127.0.0.1 localhost
127.0.1.1 yourPcName.yourNetwork.tld
如果你将此文件更改为
127.0.0.1 localhost
127.0.1.1 yourPcName.yourNetwork.tld
your.real.ip.here yourPcName 则主机名应该能够正确解析。
测试正确解析的一种方法是使用“hostname -i”命令,在更改hosts文件之前应该会错误地打印出你的IP地址,之后应该会正确地打印出来。
当然,这对于动态IP来说是一个可怕的解决方案……

2
感谢这个提示。显然,在使用*nix系统时,Boost.Asio仅仅是读取这些值。我在Ubuntu 9.10、10.04和Suse 11.2上测试通过。 - MOnsDaR

4

对我而言,基于解析的方法在各种边缘情况下一直不可靠。

操作系统提供了诸如以下API:

请注意,Boost.ASIO中没有调用这些函数的任何代码,因此您只能使用本地域解析方法。

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

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

2
跨平台,但这只是因为有 #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

3
ifa_addr在Windows实现中不存在。将addr->ifa_addr替换为addr->FirstUnicastAddress->Address.lpSockaddr似乎可以解决问题。 - jc211
1
在 MS Windows 部分的 res.push_back 行中,addr->ifa_addr 应该实际上是 uaddr->Address.lpSockaddr 吗?否则,它将一遍又一遍地添加单播地址列表中的第一个 IP 地址。除此之外...感谢您提供的出色答案!这正是我所需要的。 - ulatekh
@ulatekh 可能是的。我一直在使用这个来获取每个接口的IP地址,以便我可以通过特定的接口发送多播数据包,因此我不需要与每个接口关联的所有IP地址。我身边没有Windows机器,所以如果有人可以测试一下上面的代码更改,那么应该更新它。 - tstenner

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

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