42、网络接口查询:getifaddrs()获取本机IP地址、网络接口信息
说实话,网络编程里最基础也最容易踩坑的一个需求,就是获取本机的IP地址。很多新手上来就想着读 /proc/net 或者解析 ifconfig 的输出,嗯,我当年也干过这种事。后来在做一个跨平台监控工具的时候,发现这些方法既不优雅也不可靠,直到遇到了 getifaddrs() 这个函数——它才是 POSIX 系统下获取网络接口信息的正统方式。
getifaddrs() 是什么?
简单说,这个函数能一次性把系统里所有网络接口的信息都捞出来。包括接口名、IP地址(IPv4和IPv6都能拿)、子网掩码、广播地址、甚至MAC地址。它返回的是一个链表,每个节点对应一个网络接口的地址信息。
我个人习惯把它叫做“网络接口的户口本”——你想查哪个接口的什么信息,翻翻这个链表就行了。
核心函数原型:
#include <sys/types.h>
#include <ifaddrs.h>
int getifaddrs(struct ifaddrs **ifap);
void freeifaddrs(struct ifaddrs *ifa);
调用一次 getifaddrs(),系统会动态分配内存,把接口信息填进 struct ifaddrs 结构体链表里。用完记得调用 freeifaddrs() 释放,不然内存泄漏等着你。
struct ifaddrs 结构体详解
这个结构体是理解整个函数的关键。我把它拆开来讲:
| 成员 | 类型 | 说明 |
|---|---|---|
ifa_next |
struct ifaddrs * |
指向下一个接口节点,链表结构 |
ifa_name |
char * |
接口名称,比如 "eth0"、"lo"、"wlan0" |
ifa_flags |
unsigned int |
接口标志位,比如 IFF_UP、IFF_RUNNING、IFF_LOOPBACK |
ifa_addr |
struct sockaddr * |
接口的IP地址(可能是IPv4或IPv6) |
ifa_netmask |
struct sockaddr * |
子网掩码 |
ifa_broadaddr |
struct sockaddr * |
广播地址(仅对IPv4有效) |
ifa_data |
void * |
协议族特定的附加数据,一般用不到 |
这里有个坑要注意:ifa_addr 是 struct sockaddr * 类型,你不能直接拿来用。得根据 sa_family 判断是 AF_INET(IPv4)还是 AF_INET6(IPv6),然后强转成对应的结构体。我曾经在项目里忘了做这个判断,结果打印出来的IP地址全是乱码……排查了半天。
核心逻辑流程图
下面这张图展示了用 getifaddrs() 获取本机IP的完整流程:
完整代码示例:获取本机所有IP地址
下面这个例子,是我在实际项目中提炼出来的。它能打印出所有非回环接口的IPv4和IPv6地址:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
int main() {
struct ifaddrs *ifap, *ifa;
struct sockaddr_in *sa;
struct sockaddr_in6 *sa6;
char addr_buf[INET6_ADDRSTRLEN];
// 调用 getifaddrs 获取接口信息
if (getifaddrs(&ifap) == -1) {
perror("getifaddrs");
return 1;
}
printf("本机网络接口信息:\n");
printf("==================\n");
// 遍历链表
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
// 跳过没有地址的接口
if (ifa->ifa_addr == NULL) continue;
// 判断地址族
if (ifa->ifa_addr->sa_family == AF_INET) {
// IPv4 地址
sa = (struct sockaddr_in *)ifa->ifa_addr;
inet_ntop(AF_INET, &sa->sin_addr, addr_buf, sizeof(addr_buf));
printf("接口: %-8s IPv4: %s", ifa->ifa_name, addr_buf);
// 顺便打印子网掩码
if (ifa->ifa_netmask != NULL) {
sa = (struct sockaddr_in *)ifa->ifa_netmask;
inet_ntop(AF_INET, &sa->sin_addr, addr_buf, sizeof(addr_buf));
printf(" 掩码: %s", addr_buf);
}
printf("\n");
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
// IPv6 地址
sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
inet_ntop(AF_INET6, &sa6->sin6_addr, addr_buf, sizeof(addr_buf));
printf("接口: %-8s IPv6: %s\n", ifa->ifa_name, addr_buf);
}
}
// 释放内存
freeifaddrs(ifap);
return 0;
}
编译与运行
编译的时候不需要链接额外的库,直接 gcc 就行:
gcc -o getifaddr getifaddr.c
./getifaddr
输出效果大概是这样:
本机网络接口信息:
==================
接口: lo IPv4: 127.0.0.1 掩码: 255.0.0.0
接口: lo IPv6: ::1
接口: eth0 IPv4: 192.168.1.100 掩码: 255.255.255.0
接口: eth0 IPv6: fe80::211:22ff:fe33:4455
接口: wlan0 IPv4: 10.0.0.5 掩码: 255.255.255.0
小技巧: 如果你想只获取某个特定接口(比如 eth0)的IP,可以在遍历时加个 strcmp(ifa->ifa_name, "eth0") == 0 的判断。我在写网络配置工具时经常这么干。
避坑指南
嗯,这里我要说几个我踩过的坑:
- 内存泄漏: 调用
getifaddrs()后,一定要记得freeifaddrs()。我刚开始用的时候漏过一次,程序跑了三天,内存涨了200多MB…… - 地址族判断: 别想当然地认为
ifa_addr一定是AF_INET。现在IPv6越来越普及,不判断sa_family直接强转,轻则打印乱码,重则段错误。 - 回环接口: 如果你只想获取“真正”的网卡IP,记得用
ifa_flags & IFF_LOOPBACK过滤掉 lo 接口。我曾经在写服务端程序时没过滤,结果客户端连上来的是127.0.0.1…… - 线程安全:
getifaddrs()本身是线程安全的,但返回的链表是动态分配的,多线程环境下要注意同步释放。
特别注意: 在容器或虚拟化环境下,getifaddrs() 返回的接口信息可能和宿主机不一致。比如 Docker 容器里可能只有 eth0 和 lo,没有物理网卡的信息。做运维工具时一定要考虑到这种情况。
进阶用法:获取MAC地址
你可能想问:那MAC地址怎么拿?其实 getifaddrs() 本身不直接提供MAC地址。但有个取巧的办法——当 ifa_addr->sa_family == AF_PACKET 时,ifa_addr 实际上是一个 struct sockaddr_ll,里面包含了硬件地址信息。不过这个用法比较底层,我一般建议用 ioctl(SIOCGIFHWADDR) 来拿MAC,更直接。
好了,关于 getifaddrs() 的核心用法就这些。说白了,它就是一把万能钥匙,能打开系统网络接口信息的大门。你只要记住“调用-遍历-判断-释放”这四个步骤,基本不会出大问题。