C++ 检测 IPv4 和 IPv6 地址合法性
C++ 检测 IPv4 和 IPv6 地址合法性
概述
在网络编程中,经常需要验证 IP 地址的合法性。本文介绍两种在 C++ 中检测 IPv4 和 IPv6 地址合法性的方法:使用系统原生接口和手动解析验证。系统接口方法简单可靠,推荐优先使用;手动解析方法适用于无法使用系统接口的特殊场景。
方法一:使用系统原生接口(推荐)
利用 POSIX 标准中的 inet_pton
函数,该函数可同时支持 IPv4 和 IPv6 地址的验证,能正确处理各种合法格式(包括 IPv6 的压缩格式)。
#include <iostream>
#include <string>
#include <arpa/inet.h> // 包含inet_pton函数// 检测IPv4地址合法性
bool is_valid_ipv4(const std::string& ip) {struct in_addr addr;// inet_pton返回1表示成功,0表示格式错误,-1表示地址族错误return inet_pton(AF_INET, ip.c_str(), &addr) == 1;
}// 检测IPv6地址合法性
bool is_valid_ipv6(const std::string& ip) {struct in6_addr addr;return inet_pton(AF_INET6, ip.c_str(), &addr) == 1;
}// 检测是否为合法IP(IPv4或IPv6)
bool is_valid_ip(const std::string& ip) {return is_valid_ipv4(ip) || is_valid_ipv6(ip);
}int main() {// 测试用例std::string ipv4_test1 = "192.168.1.1";std::string ipv4_test2 = "256.0.0.1"; // 非法(256超过255)std::string ipv6_test1 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";std::string ipv6_test2 = "2001:db8:85a3::8a2e:370:7334"; // 压缩格式std::string ipv6_test3 = "2001:db8:85a3:::8a2e:370:7334"; // 非法(双冒号过多)std::cout << ipv4_test1 << " 是" << (is_valid_ipv4(ipv4_test1) ? "" : "非") << "合法IPv4\n";std::cout << ipv4_test2 << " 是" << (is_valid_ipv4(ipv4_test2) ? "" : "非") << "合法IPv4\n";std::cout << ipv6_test1 << " 是" << (is_valid_ipv6(ipv6_test1) ? "" : "非") << "合法IPv6\n";std::cout << ipv6_test2 << " 是" << (is_valid_ipv6(ipv6_test2) ? "" : "非") << "合法IPv6\n";std::cout << ipv6_test3 << " 是" << (is_valid_ipv6(ipv6_test3) ? "" : "非") << "合法IPv6\n";return 0;
}
代码解析
-
核心函数:
inet_pton
:将点分十进制(IPv4)或冒号分隔(IPv6)的 IP 地址字符串转换为二进制格式- 函数返回值:1 表示转换成功(地址合法),0 表示格式错误,-1 表示地址族错误
-
函数说明:
is_valid_ipv4
:检测 IPv4 地址,使用AF_INET
地址族is_valid_ipv6
:检测 IPv6 地址,使用AF_INET6
地址族is_valid_ip
:检测是否为合法 IP 地址(IPv4 或 IPv6)
-
测试用例:
- 包含合法和非法的 IPv4 地址
- 包含完整格式、压缩格式和非法格式的 IPv6 地址
编译与运行
-
Linux/macOS:
g++ ip_checker.cpp -o ip_checker && ./ip_checker
-
Windows(需要链接 Winsock 库):
g++ ip_checker.cpp -o ip_checker -lws2_32 && ip_checker.exe
方法二:手动解析验证
当无法使用系统接口时,可以通过手动解析字符串来验证 IP 地址格式。这种方法需要自己处理各种格式规则,实现相对复杂。
IPv4 手动验证
#include <string>
#include <sstream>
#include <vector>
#include <cctype>// 分割字符串
std::vector<std::string> split(const std::string& s, char delim) {std::vector<std::string> parts;std::string part;std::istringstream iss(s);while (std::getline(iss, part, delim)) {parts.push_back(part);}return parts;
}// 手动检测IPv4(点分十进制)
bool is_valid_ipv4_manual(const std::string& ip) {auto parts = split(ip, '.');// 必须有4个部分if (parts.size() != 4) return false;for (const auto& part : parts) {// 每个部分不能为空且长度不能超过3if (part.empty() || part.size() > 3) return false;// 检查是否全为数字for (char c : part) {if (!std::isdigit(c)) return false;}// 检查范围是否在0-255之间int num = std::stoi(part);if (num < 0 || num > 255) return false;// 检查是否有前导零(如"01"是非法的,"0"是合法的)if (part.size() > 1 && part[0] == '0') return false;}return true;
}
IPv6 手动验证
// 手动检测IPv6(冒号分隔)
bool is_valid_ipv6_manual(const std::string& ip) {auto parts = split(ip, ':');// IPv6地址段数应在2-8之间if (parts.size() < 2 || parts.size() > 8) return false;// 检查压缩符"::"是否出现且仅出现一次int double_colon_count = 0;for (const auto& part : parts) {if (part.empty()) {double_colon_count++;}}if (double_colon_count > 1) return false;// 检查每个段是否为合法的十六进制值for (const auto& part : parts) {if (part.empty()) continue; // 跳过压缩段// 每个段长度不能超过4if (part.size() > 4) return false;// 检查是否为十六进制字符for (char c : part) {if (!std::isxdigit(c)) return false;}}return true;
}
手动解析注意事项
-
IPv4 验证要点:
- 必须由 4 个部分组成,用点号分隔
- 每个部分为 0-255 之间的整数
- 不能有前导零(除非该部分就是 0)
-
IPv6 验证要点:
- 必须由 2-8 个部分组成,用冒号分隔
- 每个部分为 0-FFFF 之间的十六进制数
- 支持压缩格式(
::
),但只能出现一次 - 不处理特殊地址(如本地回环地址
::1
)的特殊验证
-
局限性:
- 手动解析容易遗漏边缘情况
- 不支持所有合法格式(如 IPv6 的内嵌 IPv4 地址)
- 实现复杂,维护成本高
总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
系统接口(inet_pton ) | 简单可靠、支持所有合法格式、维护成本低 | 依赖系统接口 | 大多数常规场景 |
手动解析 | 不依赖系统接口 | 实现复杂、易出错、不支持所有格式 | 无法使用系统接口的特殊场景 |
推荐优先使用系统接口方法,它能正确处理各种复杂的 IP 地址格式,且实现简单可靠。在必须使用手动解析的场景下,应进行充分的测试,覆盖各种边缘情况。