在C++中获取本地IP地址

6
我有一段代码可以获取本地 IP 地址。这是我使用的代码。
typedef std::map<string,string> settings_t;

void loadLocalIp (settings_t &ipConfig)
{
    struct ifaddrs * ifAddrStruct=NULL;
    struct ifaddrs * ifa=NULL;
    void * tmpAddrPtr=NULL;      

    getifaddrs(&ifAddrStruct);

    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa ->ifa_addr->sa_family==AF_INET) { // check it is IP4
            // is a valid IP4 Address
            tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addressBuffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);

            string key(ifa->ifa_name);
            string value(addressBuffer);
            cout<<key<<" =1 " <<value<<endl;
            ipConfig.insert(std::pair<string,string>(key, value));

           // printf("'%s': %s\n", ifa->ifa_name, addressBuffer); 
         }
     }
    if (ifAddrStruct!=NULL) 
        freeifaddrs(ifAddrStruct);//remember to free ifAddrStruct
}

int main()
{
    settings_t ipConfig;
    loadLocalIp(ipConfig);
    cout<<ipConfig.at("enp2s0")<<endl;
    return 0;
}

因此,我的结果是:
lo =1 127.0.0.1
enp2s0 =1 172.20.55.6
172.20.55.6

但是在另一台电脑上,接口名称是不同的。他们得到了如下结果:
lo =1 127.0.0.1
ens32 =1 172.20.55.9
terminate called after throwing an instance of 'std::out_of_range'
  what():  map::at
Aborted (core dumped)

我希望无论接口名称如何,都能够获取我的IP地址。如果接口名称因不同计算机而异,我该如何获取本地IP地址。无论接口名称如何,它应该提供IP地址。我该怎么做?

我的问题是,目前我正在使用此方法获取本地IP。但是我应该获取任何接口名称的IP。一件事,我需要找到该接口名称并将其应用于上述代码中,或者是否有任何其他选项可以在不使用该接口的情况下查找该IP?


1
你硬编码了接口名称 ipConfig.at("enp2s0")。为什么不在创建后直接使用映射中的内容呢? - Galik
是的。我的问题是,现在我正在使用这种方法获取本地IP。但是我应该获得无论接口名称是什么的IP。有一件事,我需要找到那个接口名称并将其应用于我的上述代码(或者)是否有其他选项可以在没有该接口的情况下找到该IP? - Smith Dwayne
1
计算机可以有多个接口,那么你寻找的是哪一个标准呢?比如说,你想要连接到互联网的接口吗? - Galik
这里就变得棘手了(超出了我的知识范围),因为计算机可以连接到多个不同的网络,其中任何一个都可能连接到互联网(甚至所有网络都可以)。如何处理这个问题可能取决于你正在做什么。我猜你可以通过检查“路由表”并查找“默认网关”来找到你想要的内容。 - Galik
1
也许如果您在问题中提供更多关于您要做什么的信息,会有人更有能力回答? - Galik
显示剩余4条评论
4个回答

19

无论接口名称是什么,我都想获取我的IP地址。

通过查看网络接口来可靠地获取本地IP地址是困难的。正如您已经发现的那样,网络接口名称可能对于您运行的每个主机是唯一的。为了进一步复杂化事情,计算机可能具有多个网络接口,每个网络接口可能连接或未连接到互联网。

您不需要使用默认接口。更简单的方法是让操作系统路由表为您找出解决方案。您可以通过建立与某些外部服务器的套接字连接,然后调用getsockname来获取本地地址。此示例使用Google的DNS服务器8.8.8.8建立套接字连接,但您可以使用任何外部服务器。

#include <iostream>     ///< cout
#include <cstring>      ///< memset
#include <errno.h>      ///< errno
#include <sys/socket.h> ///< socket
#include <netinet/in.h> ///< sockaddr_in
#include <arpa/inet.h>  ///< getsockname
#include <unistd.h>     ///< close

int main()
{
    const char* google_dns_server = "8.8.8.8";
    int dns_port = 53;

    struct sockaddr_in serv;
    int sock = socket(AF_INET, SOCK_DGRAM, 0);

    //Socket could not be created
    if(sock < 0)
    {
        std::cout << "Socket error" << std::endl;
    }

    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = inet_addr(google_dns_server);
    serv.sin_port = htons(dns_port);

    int err = connect(sock, (const struct sockaddr*)&serv, sizeof(serv));
    if (err < 0)
    {
        std::cout << "Error number: " << errno
            << ". Error message: " << strerror(errno) << std::endl;
    }

    struct sockaddr_in name;
    socklen_t namelen = sizeof(name);
    err = getsockname(sock, (struct sockaddr*)&name, &namelen);

    char buffer[80];
    const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, 80);
    if(p != NULL)
    {
        std::cout << "Local IP address is: " << buffer << std::endl;
    }
    else
    {
        std::cout << "Error number: " << errno
            << ". Error message: " << strerror(errno) << std::endl;
    }

    close(sock);
    return 0;
}

3
如果你的应用程序需要互联网访问,那么这很优雅。但如果不需要互联网访问,那么这将使你的解决方案不够自包含,这有点糟糕... - Lightness Races in Orbit
@Justin Randall:请也看一下我的回答。 - Smith Dwayne
@LightnessRaceswithMonica 这正是我所想的;请查看我的答案,其中提供了一种无需连接的解决方案。 - SpectreVert

11

这些答案都不够好:要么走接口太麻烦了,要么需要连接互联网。

这里提供一种基于Justin Randall的回答的方法。 它基本上相同,但它连接一个UDP套接字而不是TCP。根据udp(7),在未绑定UDP套接字上使用connect(3)

将自动分配一个空闲的本地端口[...]并将套接字绑定到INADDR_ANY

此外,与TCP套接字相反,UDP套接字上的connect(3)不会产生任何网络开销或通信,因为它只更改有关在套接字缓冲区上保留哪个数据包和丢弃哪个数据包的规则。

因此,连接到任何不是INADDR_LOOPBACK的IP地址就足以检索选择绑定套接字的本地地址。

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cstring>
#include <iostream>

int main(void) {
    int sock = socket(PF_INET, SOCK_DGRAM, 0);
    sockaddr_in loopback;

    if (sock == -1) {
        std::cerr << "Could not socket\n";
        return 1;
    }

    std::memset(&loopback, 0, sizeof(loopback));
    loopback.sin_family = AF_INET;
    loopback.sin_addr.s_addr = 1337;   // can be any IP address
    loopback.sin_port = htons(9);      // using debug port

    if (connect(sock, reinterpret_cast<sockaddr*>(&loopback), sizeof(loopback)) == -1) {
        close(sock);
        std::cerr << "Could not connect\n";
        return 1;
    }

    socklen_t addrlen = sizeof(loopback);
    if (getsockname(sock, reinterpret_cast<sockaddr*>(&loopback), &addrlen) == -1) {
        close(sock);
        std::cerr << "Could not getsockname\n";
        return 1;
    }

    close(sock);

    char buf[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &loopback.sin_addr, buf, INET_ADDRSTRLEN) == 0x0) {
        std::cerr << "Could not inet_ntop\n";
        return 1;
    } else {
        std::cout << "Local ip address: " << buf << "\n";
    }
}


这只会给你环回接口的IP地址吧?即127.0.0.1吗?我知道OP说“本地IP”,但从他们问题的内容来看,这不是他们的意思。 - Lightness Races in Orbit
1
@LightnessRaceswithMonica 不不不,这提供了局域网角度的地址,即192.168.1.60。 - SpectreVert
更新了答案并附上了简要解释。 - SpectreVert
2
这段代码和@JustinRandall的答案不同,如果没有互联网将无法正常工作: loopback.sin_addr.s_addr = INADDR_LOOPBACK;应改为loopback.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 没有使用htonl时,大多数主机实际上会连接到"1.0.0.127"一个合法的互联网IP。 正如@LightnessRacesinOrbit所说,连接到环回地址只会返回环回地址。 - SamBob
1
@SamBob,所以这似乎是一个完全有效的参数。然而,我记得为什么我首先删除了htonl():正如你所说,使用真实的回环地址不仅连接到它,而且还将套接字绑定到它。实际上,上面的代码发生的是,使用UDP套接字的connect()不会产生任何网络开销,它只改变了哪些数据包被丢弃和哪些不被丢弃的规则。在connect()改变规则之前,套接字执行了bind(),就像在man connect(3)中所说的那样,绑定到一个未使用的本地地址。我会更新答案,使其更加清晰。 - SpectreVert

0

感谢您的解决方案。它很好用。但是当我搜索解决方案时,我也找到了以下答案。请看一下。这个答案有什么优缺点。

 FILE *f;
    char line[100] , *p , *c;

    f = fopen("/proc/net/route" , "r");

    while(fgets(line , 100 , f))
    {
        p = strtok(line , " \t");
        c = strtok(NULL , " \t");

        if(p!=NULL && c!=NULL)
        {
            if(strcmp(c , "00000000") == 0)
            {
                printf("Default interface is : %s \n" , p);
                break;
            }
        }
    }

    //which family do we require , AF_INET or AF_INET6
    int fm = AF_INET;
    struct ifaddrs *ifaddr, *ifa;
    int family , s;
    char host[NI_MAXHOST];

    if (getifaddrs(&ifaddr) == -1) 
    {
        perror("getifaddrs");
        exit(EXIT_FAILURE);
    }

    //Walk through linked list, maintaining head pointer so we can free list later
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) 
    {
        if (ifa->ifa_addr == NULL)
        {
            continue;
        }

        family = ifa->ifa_addr->sa_family;

        if(strcmp( ifa->ifa_name , p) == 0)
        {
            if (family == fm) 
            {
                s = getnameinfo( ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6) , host , NI_MAXHOST , NULL , 0 , NI_NUMERICHOST);

                if (s != 0) 
                {
                    printf("getnameinfo() failed: %s\n", gai_strerror(s));
                    exit(EXIT_FAILURE);
                }

                printf("address: %s", host);
            }
            printf("\n");
        }
    }

    freeifaddrs(ifaddr);

    return 0;

0
获取本地IP的酷方法是执行ipconfig命令,将输出保存到文件中并读取它,然后解析数据,以便输出仅显示您的IPv4地址。例如,可以使用以下代码实现:
std::string GetParsedIPConfigData(std::string Columb)
{
    //Set up command file path and command line command
    std::string APPDATA = getenv("APPDATA");
    std::string path = APPDATA + "\\localipdata.txt";
    std::string cmd =  "ipconfig > " + path;

    //execute ipconfig command and save file to path
    system(cmd.c_str());

    //current line
    std::string line;

    //Line array : Here is all lines saved
    std::string lineArray[500];
    int arrayCount = 0;

    std::ifstream file(path);
    if (file.is_open())
    {
        //Get all lines
        while (std::getline(file, line))
        {
            //Save each line into a element in an array
            lineArray[arrayCount] = line;
            arrayCount++;
        }

        for (int arrayindex = 0; arrayindex <= arrayCount; arrayindex++)
        {
            std::string s = Columb;
            std::string s2 = ":";

            //Search all lines and get pos
            std::size_t i = lineArray[arrayindex].find(s);
            std::size_t i2 = lineArray[arrayindex].find(s2);

            //Found a match for Columb
            if (lineArray[arrayindex].find(s) != std::string::npos)
            {
                //Validate
                if (i != std::string::npos)
                {
                    //Earse Columb name
                    lineArray[arrayindex].erase(i, s.length());

                    //Erase all blanks
                    lineArray[arrayindex].erase(remove_if(lineArray[arrayindex].begin(), lineArray[arrayindex].end(), isspace), lineArray[arrayindex].end());

                    //Found match for ':'
                    if (lineArray[arrayindex].find(s2) != std::string::npos)
                    {
                        //Validate
                        if (i2 != std::string::npos)
                        {
                            //Delete all characters prior to ':'
                            lineArray[arrayindex].erase(0, lineArray[arrayindex].find(":"));
                            lineArray[arrayindex].erase(std::remove(lineArray[arrayindex].begin(), lineArray[arrayindex].end(), ':'), lineArray[arrayindex].end());
                        }
                    }
                    //Return our data
                    return lineArray[arrayindex];
                }
            } 

            //Only go through all lines once
            if (arrayindex == arrayCount)
                break; 
        } 

        //Close file
        file.close();
    }
    //Something went wrong
    return "Invalid";
}

然后像这样调用它:

cout << parser.GetParsedIPConfigData("IPv4 Address") << "\n\n";

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