🌐 Unix/Linux 平台通过 IP 地址获取接口名的 C++ 实现
📌 函数声明
ppp::string UnixAfx::GetInterfaceName(const IPEndPoint& address) noexcept {
- 功能:根据给定的 IP 端点(
IPEndPoint
)获取对应的网络接口名称(如eth0
)。 - 返回值:成功返回接口名,失败返回空字符串。
- 平台限制:Android 需 API ≥ 24(Android 7.0+)。
🔍 代码逐行解析(含中文注释)
#if (!defined(_ANDROID) || __ANDROID_API__ >= 24)
🔹 平台兼容性检查
- 仅在非 Android 或 Android API ≥ 24 时编译以下代码。
struct ifaddrs* ifa = NULL;
if (getifaddrs(&ifa)) {
return "";
}
🔹 获取网络接口列表
getifaddrs
获取所有网络接口信息链表头ifa
。- 失败时返回空字符串(如权限不足)。
struct ifaddrs* oifa = ifa; // 保存链表头用于后续释放内存
while (NULL != ifa) {
struct sockaddr* addr = ifa->ifa_addr;
if (NULL != addr) {
🔹 遍历接口链表
oifa
备份链表头指针,确保后续能正确释放内存。- 跳过无地址信息的接口(
ifa_addr
为NULL
)。
switch (addr->sa_family) {
case AF_INET: { // IPv4 地址
if (address.GetAddressFamily() != AddressFamily::InterNetwork) {
break;
}
🔹 处理 IPv4 地址
- 检查目标地址是否为 IPv4 类型(
InterNetwork
)。 - 类型不匹配则跳过当前接口。
struct sockaddr_in* in4_addr = (struct sockaddr_in*)addr;
if (in4_addr->sin_addr.s_addr != address.GetAddress()) {
break;
}
🔹 比较 IPv4 地址
- 将接口地址转为
sockaddr_in
结构。 - 对比接口的 IPv4 地址(
s_addr
)与目标地址是否一致。
freeifaddrs(oifa); // 释放接口链表内存
return ifa->ifa_name; // 返回匹配的接口名
}
🔹 匹配成功处理
- 释放链表内存,返回当前接口名称(如
"eth0"
)。
case AF_INET6: { // IPv6 地址
if (address.GetAddressFamily() != AddressFamily::InterNetworkV6) {
break;
}
🔹 处理 IPv6 地址
- 检查目标地址是否为 IPv6 类型(
InterNetworkV6
)。
struct sockaddr_in6* in6_addr = (struct sockaddr_in6*)addr; {
int length;
Byte* address_bytes = address.GetAddressBytes(length);
length = std::min<int>(sizeof(in6_addr->sin6_addr), length);
if (memcmp(&in6_addr->sin6_addr, address_bytes, length) != 0) {
break;
}
}
🔹 比较 IPv6 地址
- 获取目标地址的字节数组(
address_bytes
)。 - 使用
memcmp
比较接口的 IPv6 地址(128 位)是否一致。
freeifaddrs(oifa);
return ifa->ifa_name; // 返回匹配的接口名
}
};
}
ifa = ifa->ifa_next; // 移动到下一个接口节点
}
🔹 继续遍历
- 若当前接口不匹配,移动到链表下一个节点。
if (NULL != oifa) {
freeifaddrs(oifa); // 释放整个接口链表
}
#endif
return ""; // 未找到匹配接口
}
🔹 收尾处理
- 遍历结束后释放接口链表内存。
- 返回空字符串表示未找到匹配接口。
🧩 完整代码实现
ppp::string UnixAfx::GetInterfaceName(const IPEndPoint& address) noexcept {
#if (!defined(_ANDROID) || __ANDROID_API__ >= 24)
struct ifaddrs* ifa = NULL;
// 获取所有网络接口信息链表
if (getifaddrs(&ifa)) {
return ""; // 获取失败返回空
}
struct ifaddrs* oifa = ifa; // 备份链表头指针
while (NULL != ifa) {
struct sockaddr* addr = ifa->ifa_addr;
if (NULL != addr) {
switch (addr->sa_family) {
case AF_INET: { // IPv4 处理分支
// 检查地址族是否匹配
if (address.GetAddressFamily() != AddressFamily::InterNetwork) {
break;
}
struct sockaddr_in* in4_addr = (struct sockaddr_in*)addr;
// 比较 IPv4 地址是否一致
if (in4_addr->sin_addr.s_addr != address.GetAddress()) {
break;
}
freeifaddrs(oifa); // 释放链表内存
return ifa->ifa_name; // 返回接口名
}
case AF_INET6: { // IPv6 处理分支
if (address.GetAddressFamily() != AddressFamily::InterNetworkV6) {
break;
}
struct sockaddr_in6* in6_addr = (struct sockaddr_in6*)addr; {
int length;
// 获取目标地址的二进制形式
Byte* address_bytes = address.GetAddressBytes(length);
// 安全比较长度(IPv6 固定为 16 字节)
length = std::min<int>(sizeof(in6_addr->sin6_addr), length);
// 内存比较 IPv6 地址
if (memcmp(&in6_addr->sin6_addr, address_bytes, length) != 0) {
break;
}
}
freeifaddrs(oifa);
return ifa->ifa_name; // 返回接口名
}
};
}
ifa = ifa->ifa_next; // 遍历下一个接口
}
if (NULL != oifa) {
freeifaddrs(oifa); // 释放整个链表
}
#endif
return ""; // 未找到匹配接口
}
💎 关键设计总结
- 跨平台兼容性
- 通过
#if
确保 Android 低版本不编译此逻辑。
- 通过
- 资源安全管理
- 使用
oifa
备份链表头,确保任何退出路径都能正确释放内存。
- 使用
- 双协议栈支持
- 独立处理 IPv4/IPv6 地址,通过
memcmp
精确匹配 IPv6 地址。
- 独立处理 IPv4/IPv6 地址,通过
- 高效遍历
- 链表遍历在匹配成功后立即退出,避免无效搜索。
提示:实际使用时需确保
IPEndPoint
类正确实现GetAddress()
(IPv4)和GetAddressBytes()
(IPv6)方法。