前言
要判断一个IP地址是否合法,可以使用C语言标准库中的inet_pton()函数。
inet_pton() 函数是一个标准的C库函数,用于将一个IPv4或IPv6地址的字符串表示转换为网络字节序的二进制值。
inet_pton()函数可以根据传入的地址族(Address Family)参数,将一个字符串表示的IP地址转换为相应的二进制形式。如果转换成功,函数返回1,则表示该字符串ip地址合法。
一、inet_pton函数简介
NAME
inet_pton - convert IPv4 and IPv6 addresses from text to binary form
SYNOPSIS
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
此函数将字符串src转换为af地址族中的网络地址结构,然后将网络地址结构复制到dst。af参数必须是AF_INET(IPv4)或AF_INET6(IPv6)。
当前支持以下地址族:
(1)AF_INET
src指向一个字符串,该字符串包含十进制点格式的IPv4网络地址“ddd.ddd.ddd.ddd”,其中ddd是0到255范围内最多三位的十进制数。地址被转换为 struct in_addr 并复制到dst,dst的大小必须为(struct in_addr)(4)字节(32位)长。
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
(2)AF_INET6
src指向一个包含IPv6网络地址的字符串。地址被转换为 struct in6_addr 并复制到dst,dst的大小必须为(struct in6_addr)(16)字节(128位)长。
/* IPv6 address */
struct in6_addr
{
union
{
uint8_t __u6_addr8[16];
uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
} __in6_u;
#define s6_addr __in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16 __in6_u.__u6_addr16
# define s6_addr32 __in6_u.__u6_addr32
#endif
};
允许的IPv6地址格式遵循以下规则:
1.首选格式为:
x:x:x:x:x:x:x:x //一个x表示一个十六进制数,即两个字节
这种形式由八个十六进制数组成,每个十六进制数表示一个16位的值。
2.首选格式的一系列连续零值可以缩写为 ::。一个地址中只能出现 :: 的一个实例。例如,环回地址0:0:0:0:0:0:1可以缩写为 ::1。通配符地址由全零组成,可以写成 ::。
3.另一种格式可用于表示IPv4映射的IPv6地址。这种形式写为:
x:x:x:x:x:x:d.d.d.d
其中六个前导xs是十六进制值,定义地址的六个最高有效的16位(即96位),而ds以点十进制表示法表示一个值,该值定义地址的最低有效32位。这样一个地址的例子是:
::FFFF:204.152.189.116
inet_pton()在成功时返回1(网络地址已成功转换)。如果src不包含表示指定地址族中有效网络地址的字符串,则返回0。如果af不包含有效的地址族,则返回-1,并将errno设置为EAFNOSUPPORT。
二、使用例子
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char **argv) {
const char* ip_addr_str = argv[1];
struct in_addr ipv4_addr;
struct in6_addr ipv6_addr;
if (inet_pton(AF_INET, ip_addr_str, &ipv4_addr) == 1) {
printf("%s is an IPv4 address.\n", ip_addr_str);
} else if (inet_pton(AF_INET6, ip_addr_str, &ipv6_addr) == 1) {
printf("%s is an IPv6 address.\n", ip_addr_str);
} else {
printf("Failed to convert %s to binary representation.the ip_str is not vaild address\n", ip_addr_str);
}
return 0;
}
如果要判断一个IP地址是否合法,只需要调用inet_pton()函数,并检查其返回值即可。如果返回值为1,说明IP地址合法,否则说明IP地址不合法。
上述代码说明首先定义了一个IP地址字符串,然后创建了一个 struct in_addr 类型的变量 ipv4_addr,用于存储IPv4地址的二进制值,以及一个 struct in6_addr 类型的变量 ipv6_addr,用于存储IPv6地址的二进制值。
接下来,我们先调用 inet_pton() 函数,将IP地址字符串转换为IPv4地址的二进制值。如果转换成功,则说明该字符串表示的是IPv4地址,否则我们再调用 inet_pton() 函数,将IP地址字符串转换为IPv6地址的二进制值。如果转换成功,则说明该字符串表示的是IPv6地址,否则说明IP地址字符串格式不正确或者不属于IPv4或IPv6地址。即该字符串Ip地址不合法。
三、其他例子
要检查一个字符串是否表示一个合法的IP地址,可以使用正则表达式来验证。
下面是使用 python 和 c++11 来检查一个字符串ipv4的地址是否正确。
3.1 python
import sys
import re
def is_valid_ipv4(ip: str) -> bool:
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
if re.match(pattern, ip):
octets = ip.split('.')
if all(0 <= int(octet) <= 255 for octet in octets):
return True
return False
str_ip = sys.argv[1]
if __name__ == '__main__':
ret = is_valid_ipv4(str_ip)
if ret == True:
print('the strip {0} is vaild'.format(str_ip))
else:
print('the strip {0} is not vaild'.format(str_ip))
$ python3 ipv4.py 1.1.1.1
the strip 1.1.1.1 is vaild
(1)定义IPv4地址的正则表达式,使用了分组和重复匹配来匹配四个数字(0-255)组成的IP地址。
(2)使用 re.match() 函数来执行正则表达式匹配,如果匹配成功,返回一个 Match 对象;否则返回 None。
(3)如果匹配成功,使用 split() 函数将IP地址字符串分割为四个数字,并使用 all() 函数来检查每个数字是否在0-255之间。
(4)如果所有数字都在0-255之间,则返回 True,表示IP地址合法;否则返回 False,表示IP地址不合法。
其中:
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
这是一个正则表达式,用于匹配IPv4地址的字符串。下面是该正则表达式的各个部分的含义解释:
^:匹配字符串的开头。
(:开始一个分组。
\d{1,3}:匹配1到3位数字。
.:匹配一个字符(点号)。
):结束分组。
{3}:重复匹配前面的分组3次,即匹配三段由数字和点号组成的字符串。
\d{1,3}:匹配1到3位数字。
$:匹配字符串的结尾。
因此,该正则表达式匹配的字符串应该由四个数字段组成,每个数字段由1到3个数字组成,数字段之间用点号分隔。例如,符合该正则表达式的字符串包括 127.0.0.1、192.168.1.100 等,而不符合该正则表达式的字符串包括 127.0.0.256、192.168.1 等。
在 Python 中,使用 r 前缀可以将一个字符串标记为“原始字符串”,即字符串中的特殊字符(如反斜杠)不会被解释为转义字符。因此,r’^(\d{1,3}.){3}\d{1,3}$’ 表示一个原始字符串,其中的 \d 表示匹配任意数字字符,而 . 表示匹配任意字符(除了换行符)。
3.2 c++11
#include <iostream>
#include <regex>
#include <string>
bool is_valid_ip(std::string ip_str) {
// 定义IPv4地址正则表达式
//std::regex ipv4_regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$");
std::regex ipv4_regex("^(([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.){3}([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$");
// 执行正则表达式匹配
bool match_result = std::regex_match(ip_str, ipv4_regex);
// 如果匹配成功,返回true;否则返回false
return match_result;
}
int main() {
std::string ip_str;
std::cout << "请输入IP地址:";
std::cin >> ip_str;
if (is_valid_ip(ip_str)) {
std::cout << ip_str << " 是合法的IPv4地址" << std::endl;
} else {
std::cout << ip_str << " 不是合法的IPv4地址" << std::endl;
}
return 0;
}
C++11 引入了正则表达式库 std::regex,可以方便地使用正则表达式来匹配字符串。
其中:
std::regex ipv4_regex("^(([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.){3}([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$");
这是一个正则表达式,用于匹配IPv4地址的字符串:
^:匹配字符串的开头。
(:开始一个分组。
(:开始一个分组。
[01]?[0-9]?[0-9]:匹配0到3位数字,可以是01开头的数字(如01、02、03等),也可以是普通的0到9的数字。
|:表示或者。
2[0-4][0-9]:匹配200到249之间的数字。
|:表示或者。
25[0-5]:匹配250到255之间的数字。
):结束分组。
\\.:匹配一个点号字符(注意需要转义)。
):结束分组。
{3}:重复匹配前面的分组3次,即匹配三段由数字和点号组成的字符串。
[01]?[0-9]?[0-9]:匹配0到3位数字,可以是01开头的数字(如01、02、03等),也可以是普通的0到9的数字。
|:表示或者。
2[0-4][0-9]:匹配200到249之间的数字。
|:表示或者。
25[0-5]:匹配250到255之间的数字。
$:匹配字符串的结尾。
因此,整个正则表达式的含义是:匹配由四段数字和点号组成的字符串,每一段数字的范围为0到255,且字符串的开头和结尾不能有其他字符。这符合 IPv4 地址的格式规范。
也可以这么写:
std::regex ipv4_regex("^((\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])$");
^:匹配字符串的开头。
(:开始一个分组。
\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]:匹配一个数字段,可以是0-255之间的数字。这里的 \\d{1,2} 表示匹配 1 到 2 位数字,1\\d{2} 表示匹配 100 到 199 之间的数字,2[0-4]\\d 表示匹配 200 到 249 之间的数字,25[0-5] 表示匹配 250 到 255 之间的数字。
\\.:匹配一个点号字符(需要转义)。
):结束分组。
{3}:重复匹配前面的分组 3 次,即匹配三段由数字和点号组成的字符串。
(\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]):匹配第四段数字,可以是 0-255 之间的数字。
$:匹配字符串的结尾。