Unix/Linux 平台通过 IP 地址获取接口名的 C++ 实现
🌐 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); // 释放整个接口链表}
#endifreturn ""; // 未找到匹配接口
}
🔹 收尾处理
- 遍历结束后释放接口链表内存。
- 返回空字符串表示未找到匹配接口。
🧩 完整代码实现
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); // 释放整个链表}
#endifreturn ""; // 未找到匹配接口
}
💎 关键设计总结
- 跨平台兼容性
- 通过
#if
确保 Android 低版本不编译此逻辑。
- 通过
- 资源安全管理
- 使用
oifa
备份链表头,确保任何退出路径都能正确释放内存。
- 使用
- 双协议栈支持
- 独立处理 IPv4/IPv6 地址,通过
memcmp
精确匹配 IPv6 地址。
- 独立处理 IPv4/IPv6 地址,通过
- 高效遍历
- 链表遍历在匹配成功后立即退出,避免无效搜索。
提示:实际使用时需确保
IPEndPoint
类正确实现GetAddress()
(IPv4)和GetAddressBytes()
(IPv6)方法。