IP地址结构信息与字符串相互转化:inet 您所在的位置:网站首页 ipv4转化成数字 IP地址结构信息与字符串相互转化:inet

IP地址结构信息与字符串相互转化:inet

2024-01-28 03:30| 来源: 网络整理| 查看: 265

目录1. 问题描述2. IPv4场景解决方案2.1 inet_aton函数2.2 inet_addr函数2.3 inet_ntoa函数2.4 in_addr和sockaddr_in地址结构2.4 示例3. IPv4,IPv6场景共同解决方案3.1 inet_pton函数3.2 inet_ntop函数3.3 示例3.4 sock_ntop自定义函数

1. 问题描述

1)假如有一个IPv4地址字符串(点分十进制),如何转化为socket编程所需的sockaddr地址结构信息? 2)假如通过getaddrinfo等方式,已经取得了sockaddr地址结构信息,如何转化为字符串形式?(点分十进制 for IPv4, 冒号十六进制 for IPv6)

什么是IP地址文本形式? 点分十进制数串: 形如"192.168.0.1" 冒号十六进制数串:形如"fe80::20c:29ff:fe99:de11"

2. IPv4场景解决方案

inet_aton, inet_addr, inet_ntoa在点分十进制数串与长度为32bits的网络字节序二进制值间转换IPv4地址。 不适用于IPv6地址。

2.1 inet_aton函数

点分十进制数串 -> IPv4地址结构(in_addr) 将主机地址cp(点分十进制数串)转换成一个32bits的网络字节序二进制值,保存到inp指向的缓存。

#include #include #include int inet_aton(const char *cp, struct in_addr *inp);

参数 cp 点分十进制数串,由调用者提供, 通常cp采用形如a.b.c.d形式, 其他支持的形式参见man inet_aton手册 inp 输出参数, 指向in_addr结构。由调用者维护缓存, 函数填写内容

返回值 若cp指向的地址有效,返回非0;若无效,返回0

特别地,

如果inp为NULL,那么该函数仍然将输入的字符串执行有效性检查,但是不存储任何结果

2.2 inet_addr函数

点分十进制数串 -> IPv4地址结构(in_addr) inet_addr同inet_aton的区别是, inet_addr返回32bits的网络字节序二进制值

#include #include #include in_addr_t inet_addr(const char *cp);

该函数有个特殊情况,

所有232个可能值都是有效IP地址(0.0.0.0 到 255.255.255.255),但是出错时,函数返回INADDR_NONE(通常-1,对应32个全1的值),也就是说点分十进制数串255.255.255.255(IPv4有限广播地址)不能由该函数处理。

因此,应当避免使用inet_addr

2.3 inet_ntoa函数

IPv4地址结构(in_addr) -> 点分十进制数串 函数将32bits主机地址in转化成点分十进制数串

#include #include #include char *inet_ntoa(struct in_addr in);

参数 in 调用者传入,IPv4地址结构对象。注意到与inet_aton的参数inp的区别,in并非指针

返回值 返回static分配的缓存, 后续的调用会覆写之前的内容。因此,如果不想被覆盖,需要自行分配存储进行复制、保存。

2.4 in_addr和sockaddr_in地址结构

可以从下面看到,in_addr是sockaddr_in的一个成员,而in_addr实际上是一个32bit的无符号整型,不过被包装成的结构体。 这也就是说, 如果是想直接将点分十进制数串和ip地址信息相互转换时, 应该是与sockaddr_in.sin_addr进行转化,而不能直接与sockaddr_in对象或s_addr进行转换。

sockaddr_in结构:

/* Structure describing an Internet socket address. */ struct sockaddr_in { __SOCKADDR_COMMON (sin_); in_port_t sin_port; /* Port number. */ struct in_addr sin_addr; /* Internet address. */ /* Pad to size of `struct sockaddr'. */ unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; }; #define __SOCKADDR_COMMON(sa_prefix) \ sa_family_t sa_prefix##family #define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

in_addr结构:

typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; }; 2.4 示例

通过inet_aton,inet_addr,将指定点分十进制ip文本串转化为二进制数;然后,通过inet_ntoa将指定ip地址二进制数转化为文本串,观察是否能还原文本串。

int test() { char *ipstr = "192.168.0.1"; // char *ipstr = "255.255.255.255"; struct sockaddr_in sockaddr; int ret; char *str; /* convert ip from text to binary for IPv4 */ ret = inet_aton(ipstr, &sockaddr.sin_addr); if (ret == 0) { fprintf(stderr, "inet_aton error: invalid ip string: %s\n", ipstr); return -1; } printf("inet_aton convert ip : from %s to %d\n", ipstr, sockaddr.sin_addr.s_addr); /* convert ip from text to binary for IPv4 except 255.255.255.255 */ sockaddr.sin_addr.s_addr = inet_addr(ipstr); /* avoid to use inet_addr */ if (sockaddr.sin_addr.s_addr == INADDR_NONE) { fprintf(stderr, "inet_addr error: invalid ip string: %s\n", ipstr); return -1; } printf("inet_addr convert ip : from %s to %d\n", ipstr, sockaddr.sin_addr.s_addr); /* convert ip from binary to text for IPv4 */ str = inet_ntoa(sockaddr.sin_addr); if (str == NULL) { fprintf(stderr, "inet_ntoa error: invalid ip structure: %d\n", sockaddr.sin_addr.s_addr); return -1; } printf("inet_ntoa convert ip: from %d to %s\n", sockaddr.sin_addr.s_addr, str); return 0; }

运行结果:

inet_aton convert ip : from 192.168.0.1 to 16820416 inet_addr convert ip : from 192.168.0.1 to 16820416 inet_ntoa convert ip: from 16820416 to 192.168.0.1 3. IPv4,IPv6场景共同解决方案 3.1 inet_pton函数

IPv4和IPv6地址: 文本 -> 二进制 将地址文本串 转换成 网络字节序二进制数形式

#include int inet_pton(int af, const char *src, void *dst); 参数 af 地址家族,支持的取值(且只能是这2个): 1)AF_INET 适用于IPv4地址,只支持"ddd.ddd.ddd.ddd"这种点分十进制数串 2)AF_INET6 适用于IPv6地址 注意:这里不同于socket()的af(地址family/domain),没有AF_UNIX

src 指向IP地址的文本形式的字符串(点分十进制数串 for IPv4,冒号十六进制数串 for IPv6)

dst 指向网络地址结构(struct in_addr for IPv4, struct struct in_addr6 for IPv6)

返回值 成功,返回1(网络地址成功转换);如果在指定地址家族中,src不是一个有效网络地址文本串,则返回0;如果af不包含一个有效地址家族(非规定2个取值),则返回-1, 且errno设置为EAFNOSUPPORT。 3.2 inet_ntop函数

IPv4和IPv6地址: 二进制 -> 文本 将地址网络字节序二进制数 转换成 文本串形式

#include const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 参数 af 地址家族,支持的取值(且只能是这2个): 1)AF_INET 适用于IPv4地址,只支持"ddd.ddd.ddd.ddd"这种点分十进制数串 2)AF_INET6 适用于IPv6地址 注意:这里不同于socket()的af(地址family/domain),没有AF_UNIX

src 指向二进制地址结构,对于ipv4, src指向struct in_addr;对于ipv6,src指向struct in_addr6

dst 指向存放地址文本串的缓存,由调用者维护缓存, 函数填充内容

size 指示dst指向缓存大小

返回值 成功,返回指向dst的非NULL指针;失败, 返回NULL,errno被设置 3.3 示例

对于ipv4,方式同inet_aton, inet_addr, inet_ntoa,先将文本串转化为二进制数,打印;然后再转化为文本串,再打印。观察中间输出,以及最终是否能还原。 对于ipv6,因为ipv6地址二进制数128bit, 这里没有直接打印二进制数,而是先将已知的ipv6文本串(冒号十六进制表示法)先通过inet_pton转化为二进制串,再通过inet_ntop转换回文本串。观察是否能还原。

int test() { /* ipv4 solution */ struct sockaddr_in sockaddr; char *ipstr = "192.168.0.1"; char ip4text[INET_ADDRSTRLEN]; char *str; int ret; printf("===ipv4===\n"); /* convert ip from text to binary for IPv4 */ bzero(&sockaddr, sizeof (sockaddr)); ret = inet_pton(AF_INET, ipstr, &sockaddr.sin_addr); if (ret != 1) { if (ret == 0) { fprintf(stderr, "inet_pton error: %s is not a valid ip text\n", ipstr); } else if (ret == -1) { perror("inet_pton error"); } fprintf(stderr, "inet_pton error: unkown error"); return -1; } printf("inet_pton convert ip from %s to %d\n", ipstr, sockaddr.sin_addr.s_addr); /* convert ip from binary to text for IPv4 */ str = inet_ntop(AF_INET, &sockaddr.sin_addr, ip4text, sizeof(ip4text)); if (str == NULL) { perror("inet_ntop error"); return -1; } printf("inet_ntop convert ip from %d to %s\n", sockaddr.sin_addr.s_addr, str); /* ipv6 solution */ char *ip6str = "fe80::20c:29ff:fe99:de11"; /* inet6 from bash "$ ifconfig -a" */ char ip6text[INET6_ADDRSTRLEN]; struct sockaddr_in6 sockaddr6; printf("===ipv6===\n"); /* convert ip from text to binary for IPv6 */ bzero(&sockaddr6, sizeof (sockaddr6)); ret = inet_pton(AF_INET6, ip6str, &sockaddr6.sin6_addr); if (ret != 1) { if (ret == 0) { fprintf(stderr, "inet_pton error: %s is not a valid ipv6 text\n", ip6str); } else if (ret == -1) { perror("inet_pton error"); } fprintf(stderr, "inet_pton error: unkown error"); return -1; } /* convert ip from binary to text for IPv6 */ str = inet_ntop(AF_INET6, &sockaddr6.sin6_addr, ip6text, sizeof(ip6text)); if (str == NULL) { perror("inet_ntop error"); return -1; } printf("%s\n", str); }

运行结果:

===ipv4=== inet_pton convert ip from 192.168.0.1 to 16820416 inet_ntop convert ip from 16820416 to 192.168.0.1 ===ipv6=== fe80::20c:29ff:fe99:de11 3.4 sock_ntop自定义函数

inet_ntop存在一个问题:要求调用者传递一个指向某个二进制地址的指针src,而该指针通常包含在一个套接字地址结构中,也就是说,调用者必须直到这个结构的格式,地址家族,以及应该具体为哪个IP格式提供各自的地址结构(struct sockaddr_in for IPv4, struct sockaddr_in6 for IPv6)

i.e. 对IPv4,应该编写这样的代码:

struct sockaddr_in sockaddr; inet_ntop(AF_INET, &sockaddr.sin_addr, str, sizeof(str));

对IPv6,应该编写这样的代码:

struct sockaddr_in6 sockaddr6; inet_ntop(AF_INET6, &sockaddr6.sin_addr, str, sizeof(str));

这样,就使得代码的编写与协议相关。为解决该问题,编写sock_ntop,以指向某个套接字地址结构的指针为参数,查看该结构的内部,然后根据具体情况返回地址的文本串格式

char *sock_ntop(const struct sockaddr *sa, socklen_t addrlen);

函数实现

char *sock_ntop(const struct sockaddr *sa, socklen_t addrlen) { char portstr[8]; static char str[128]; /* struct sockaddr_storage最多128byte */ switch (sa->sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)sa; if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) return NULL; /* 转换存在错误 */ if (ntohs(sin->sin_port) != 0) { snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port)); strcat(str, portstr); } return str; } case AF_INET6: { struct sockaddr_in *sin6 = (struct sockaddr_in *)sa; if (inet_ntop(AF_INET6, &sin6->sin_addr, str, sizeof(str)) == NULL) return NULL; /* 转换存在错误 */ if (ntohs(sin6->sin_port) != 0) { snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin6->sin_port)); strcat(str, portstr); } return str; } default: errno = EAFNOSUPPORT; /* 不支持的地址family */ } return NULL; }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有