声明:
本文是我在工作中遇到的网络相关的问题,以及自己的一些总结,希望可以对你有所帮助。
介绍:
获得本地ipv4和ipv6的方式有两种,一种是通过调用getifaddrs函数而另一种是通过socket的ioctl获得ipv4,而通过/proc/net/if_inet6节点来获得ipv6。他们的具体实现方式为:
通过调用getifaddrs函数来获得本地ipv4和ipv6:
具体参考:https://man7.org/linux/man-pages/man3/getifaddrs.3.html
涉及的函数以及包含的头文件:
#include <sys/types.h>
#include <ifaddrs.h>
int getifaddrs(struct ifaddrs **ifap);
void freeifaddrs(struct ifaddrs *ifa);
getifaddrs函数的作用是:创建一个描述本地系统网络接口的结构链表,并将链表中第一项的地址存储在*ifap中。该列表由ifaddrs结构组成,定义如下:
struct ifaddrs {
struct ifaddrs *ifa_next; /* Next item in list */
char *ifa_name; /* Name of interface */
unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
struct sockaddr *ifa_addr; /* Address of interface */
struct sockaddr *ifa_netmask; /* Netmask of interface */
union {
struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */
struct sockaddr *ifu_dstaddr; /* Point-to-point destination address */
} ifa_ifu;
#define ifa_broadaddr ifa_ifu.ifu_broadaddr
#define ifa_dstaddr ifa_ifu.ifu_dstaddr
void *ifa_data; /* Address-specific data */
};
- ifa_next字段包含指向列表中的下一个结构的指针,如果这是列表的最后一项,则为空。
- ifa_name指向以空字符结束的接口名。
- ifa_addr字段指向包含接口地址的结构。(应该参考sa_family子字段来确定地址结构的格式。)这个字段可能包含一个空指针。
而要获取的ipv4或者ipv6地址主要存放在ifa_addr中。
具体的代码实现:
#define _GNU_SOURCE /* To get defns of NI_MAXSERV and NI_MAXHOST */
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/if_link.h>
int main(int argc, char *argv[])
{
struct ifaddrs *ifaddr;
int family, s;
char host[NI_MAXHOST];
if (getifaddrs(&ifaddr) == -1) { //通过getifaddrs获得ifaddrs 结构体
perror("getifaddrs");
exit(EXIT_FAILURE);
}
/* Walk through linked list, maintaining head pointer so we can free list later */
for (struct ifaddrs *ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
family = ifa->ifa_addr->sa_family; //通过family来确定包的类型
/* Display interface name and family (including symbolic form of the latter for the common families) */
printf("%-8s %s (%d)\n",
ifa->ifa_name,
(family == AF_PACKET) ? "AF_PACKET" :
(family == AF_INET) ? "AF_INET" :
(family == AF_INET6) ? "AF_INET6" : "???",
family);
/* For an AF_INET* interface address, display the address */
if (family == AF_INET || family == AF_INET6) {
s = getnameinfo(ifa->ifa_addr,
(family == AF_INET) ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6),
host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST);
if (s != 0) {
printf("getnameinfo() failed: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
printf("\t\taddress: <%s>\n", host);
} else if (family == AF_PACKET && ifa->ifa_data != NULL) {
struct rtnl_link_stats *stats = ifa->ifa_data;
printf("\t\ttx_packets = %10u; rx_packets = %10u\n"
"\t\ttx_bytes = %10u; rx_bytes = %10u\n",
stats->tx_packets, stats->rx_packets,
stats->tx_bytes, stats->rx_bytes);
}
}
freeifaddrs(ifaddr);
exit(EXIT_SUCCESS);
}
上面代码主要分为个步骤:
- 1. getifaddrs(&ifaddr) //通过getifaddrs获得ifaddrs 结构体
- 2. family = ifa->ifa_addr->sa_family; //从ifaddrs 中读取sa_family,通过family来确定包的类型
- 3. 在getnameinfo函数中,通过不用的family 来使用不用的sockaddr结构体获得不同的ipv4或者ipv6地址信息。
上面代码中使用了getnameinfo函数获取IP地址的方式,而从上面的信息中我们已经获得了ifaddrs 结构体和family ,这个时候也可以直接读ifa_addr来获得想要的信息。
上面代码对应的结果为:
通过ioctl的方式来获得ipv4同时通过/proc/net/if_inet6节点获得ipv6:
具体参考:https://domen.ipavec.net/en/get-ip-ipv6-and-mac-addresses-using-ioctl-and-procfs-linux-c/
通过/proc/net/if_inet6节点获得ipv6的方式在下面parse_inet6函数中,他的主要实现方式为:
- 1. 通过fopen("/proc/net/if_inet6", "r");来打开节点
- 2. 通过fscanf将读到的数据以指定的格式存放到对应的参数中,其中从/proc/net/if_inet6节点读到的格式为:
- 因此需要通过%2hhx来限定读取数据的长度。
- 3. 通过inet_ntop(AF_INET6, ipv6, address, sizeof(address)将读到的数字类型的ipv6地址转化为字符串类型的ip地址。
具体代码:
#include <stdio.h>
#include <string.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <dirent.h>
#define IPV6_ADDR_GLOBAL 0x0000U
#define IPV6_ADDR_LOOPBACK 0x0010U
#define IPV6_ADDR_LINKLOCAL 0x0020U
#define IPV6_ADDR_SITELOCAL 0x0040U
#define IPV6_ADDR_COMPATv4 0x0080U
void parse_inet6(const char *ifname) {
FILE *f;
int ret, scope, prefix;
unsigned char ipv6[16];
char dname[IFNAMSIZ];
char address[INET6_ADDRSTRLEN];
char *scopestr;
f = fopen("/proc/net/if_inet6", "r");
if (f == NULL) {
return;
}
while (19 == fscanf(f,
" %2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx %*x %x %x %*x %s",
&ipv6[0],
&ipv6[1],
&ipv6[2],
&ipv6[3],
&ipv6[4],
&ipv6[5],
&ipv6[6],
&ipv6[7],
&ipv6[8],
&ipv6[9],
&ipv6[10],
&ipv6[11],
&ipv6[12],
&ipv6[13],
&ipv6[14],
&ipv6[15],
&prefix,
&scope,
dname)) {
if (strcmp(ifname, dname) != 0) {
continue;
}
if (inet_ntop(AF_INET6, ipv6, address, sizeof(address)) == NULL) {
continue;
}
switch (scope) {
case IPV6_ADDR_GLOBAL:
scopestr = "Global";
break;
case IPV6_ADDR_LINKLOCAL:
scopestr = "Link";
break;
case IPV6_ADDR_SITELOCAL:
scopestr = "Site";
break;
case IPV6_ADDR_COMPATv4:
scopestr = "Compat";
break;
case IPV6_ADDR_LOOPBACK:
scopestr = "Host";
break;
default:
scopestr = "Unknown";
}
printf("IPv6 address: %s, prefix: %d, scope: %s\n", address, prefix, scopestr);
}
fclose(f);
}
void parse_ioctl(const char *ifname)
{
int sock;
struct ifreq ifr;
struct sockaddr_in *ipaddr;
char address[INET_ADDRSTRLEN];
size_t ifnamelen;
/* copy ifname to ifr object */
ifnamelen = strlen(ifname);
if (ifnamelen >= sizeof(ifr.ifr_name)) {
return ;
}
memcpy(ifr.ifr_name, ifname, ifnamelen);
ifr.ifr_name[ifnamelen] = '\0';
/* open socket */
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock < 0) {
return;
}
/* process mac */
if (ioctl(sock, SIOCGIFHWADDR, &ifr) != -1) {
printf("Mac address: %02x:%02x:%02x:%02x:%02x:%02x\n",
(unsigned char)ifr.ifr_hwaddr.sa_data[0],
(unsigned char)ifr.ifr_hwaddr.sa_data[1],
(unsigned char)ifr.ifr_hwaddr.sa_data[2],
(unsigned char)ifr.ifr_hwaddr.sa_data[3],
(unsigned char)ifr.ifr_hwaddr.sa_data[4],
(unsigned char)ifr.ifr_hwaddr.sa_data[5]);
}
/* process mtu */
if (ioctl(sock, SIOCGIFMTU, &ifr) != -1) {
printf("MTU: %d\n", ifr.ifr_mtu);
}
/* die if cannot get address */
if (ioctl(sock, SIOCGIFADDR, &ifr) == -1) {
close(sock);
return;
}
/* process ip */
ipaddr = (struct sockaddr_in *)&ifr.ifr_addr;
if (inet_ntop(AF_INET, &ipaddr->sin_addr, address, sizeof(address)) != NULL) {
printf("Ip address: %s\n", address);
}
/* try to get broadcast */
if (ioctl(sock, SIOCGIFBRDADDR, &ifr) != -1) {
ipaddr = (struct sockaddr_in *)&ifr.ifr_broadaddr;
if (inet_ntop(AF_INET, &ipaddr->sin_addr, address, sizeof(address)) != NULL) {
printf("Broadcast: %s\n", address);
}
}
/* try to get mask */
if (ioctl(sock, SIOCGIFNETMASK, &ifr) != -1) {
ipaddr = (struct sockaddr_in *)&ifr.ifr_netmask;
if (inet_ntop(AF_INET, &ipaddr->sin_addr, address, sizeof(address)) != NULL) {
printf("Netmask: %s\n", address);
}
}
close(sock);
}
int main(void)
{
DIR *d;
struct dirent *de;
d = opendir("/sys/class/net/");
if (d == NULL) {
return -1;
}
while (NULL != (de = readdir(d))) {
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
continue;
}
printf("Interface %s\n", de->d_name);
parse_ioctl(de->d_name);
parse_inet6(de->d_name);
printf("\n");
}
closedir(d);
return 0;
}
运行结果为:
socket的ioctl函数:
要看懂上面获得ipv4地址的代码需要了解socket以及其ioctl函数:建立socket后,通过ioctl获得各个参数的结构体。在Linux中,通过ioctl可以获得或者设置特定参数的数值,在网络中同样可以。
通过ioctl方式获得参数通常会通过两个结构体将参数返回,一个是ifconf:通过SIOCGIFCONF来获取所有接口的清单 。 而另一个为ifreq:获得其他的相关信息,具体的可以参考下ioctl选项与对应的数据类型:
参考:https://www.cnblogs.com/oxspirt/p/7478321.html
类别 | Request | 说明 | 数据类型 |
套接口 | SIOCATMARK SIOCSPGRP SIOCGPGRP | 是否位于带外标记 设置套接口的进程ID 或进程组ID 获取套接口的进程ID 或进程组ID | int int int |
文件 | FIONBIN FIOASYNC FIONREAD FIOSETOWN FIOGETOWN | 设置/ 清除非阻塞I/O 标志 设置/ 清除信号驱动异步I/O 标志 获取接收缓存区中的字节数 设置文件的进程ID 或进程组ID 获取文件的进程ID 或进程组ID | int int int int int |
接口 | SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFFLAGS SIOCGIFFLAGS SIOCSIFDSTADDR SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFBRDADDR SIOCGIFNETMASK SIOCSIFNETMASK SIOCGIFMETRIC SIOCSIFMETRIC SIOCGIFMTU SIOCxxx | 获取所有接口的清单 设置接口地址 获取接口地址 设置接口标志 获取接口标志 设置点到点地址 获取点到点地址 获取广播地址 设置广播地址 获取子网掩码 设置子网掩码 获取接口的测度 设置接口的测度 获取接口MTU (还有很多取决于系统的实现) | struct ifconf struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq |
ARP | SIOCSARP SIOCGARP SIOCDARP | 创建/ 修改ARP 表项 获取ARP 表项 删除ARP 表项 | struct arpreq struct arpreq struct arpreq |
路由 | SIOCADDRT SIOCDELRT | 增加路径 删除路径 | struct rtentry struct rtentry |
流 | I_xxx |
|
|
而具体这两种结构体的关系为:
参考:https://developer.aliyun.com/article/244082
而两种结构体的具体表示可以参考:
https://blog.csdn.net/qq_41453285/article/details/100567095
https://segmentfault.com/a/1190000005138358
一、struct ifconf结构体
功能:用来保存 所有接口的清单
/*
* Structure used in SIOCGIFCONF request.
* Used to retrieve interface configuration
* for machine (useful for programs which
* must know all networks accessible).
*/
struct ifconf {
int ifc_len; /* size of buffer */
union {
char __user *ifcu_buf;
struct ifreq __user *ifcu_req;
} ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */
#define ifc_req ifc_ifcu.ifcu_req /* array of structures */
二、struct ifreq结构体
功能:用来保存某个接口的信息
/*
* Interface request structure used for socket
* ioctl's. All interface ioctl's must have parameter
* definitions which begin with ifr_name. The
* remainder may be interface specific.
*/
struct ifreq {
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
而通过ifreq 结构体我们可以清楚的获得各个接口的信息。而在之前的例子中使用了这些参数:
主要是分为两个步骤:
- 1.建立socket连接
- 2. 通过ioctl的各个参数来获得各个信息:
void parse_ioctl(const char *ifname)
{
int sock;
struct ifreq ifr;
struct sockaddr_in *ipaddr;
char address[INET_ADDRSTRLEN];
size_t ifnamelen;
/* copy ifname to ifr object */
ifnamelen = strlen(ifname);
if (ifnamelen >= sizeof(ifr.ifr_name)) {
return ;
}
memcpy(ifr.ifr_name, ifname, ifnamelen);
ifr.ifr_name[ifnamelen] = '\0';
/* open socket */
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock < 0) {
return;
}
/* process mac */
if (ioctl(sock, SIOCGIFHWADDR, &ifr) != -1) {
printf("Mac address: %02x:%02x:%02x:%02x:%02x:%02x\n",
(unsigned char)ifr.ifr_hwaddr.sa_data[0],
(unsigned char)ifr.ifr_hwaddr.sa_data[1],
(unsigned char)ifr.ifr_hwaddr.sa_data[2],
(unsigned char)ifr.ifr_hwaddr.sa_data[3],
(unsigned char)ifr.ifr_hwaddr.sa_data[4],
(unsigned char)ifr.ifr_hwaddr.sa_data[5]);
}
/* process mtu */
if (ioctl(sock, SIOCGIFMTU, &ifr) != -1) {
printf("MTU: %d\n", ifr.ifr_mtu);
}
/* die if cannot get address */
if (ioctl(sock, SIOCGIFADDR, &ifr) == -1) {
close(sock);
return;
}
/* process ip */
ipaddr = (struct sockaddr_in *)&ifr.ifr_addr;
if (inet_ntop(AF_INET, &ipaddr->sin_addr, address, sizeof(address)) != NULL) {
printf("Ip address: %s\n", address);
}
/* try to get broadcast */
if (ioctl(sock, SIOCGIFBRDADDR, &ifr) != -1) {
ipaddr = (struct sockaddr_in *)&ifr.ifr_broadaddr;
if (inet_ntop(AF_INET, &ipaddr->sin_addr, address, sizeof(address)) != NULL) {
printf("Broadcast: %s\n", address);
}
}
/* try to get mask */
if (ioctl(sock, SIOCGIFNETMASK, &ifr) != -1) {
ipaddr = (struct sockaddr_in *)&ifr.ifr_netmask;
if (inet_ntop(AF_INET, &ipaddr->sin_addr, address, sizeof(address)) != NULL) {
printf("Netmask: %s\n", address);
}
}
close(sock);
}
上面代码的运行结果为:
结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系:
其实对于上面的信息我们一般最关注的是地址的信息,在ifreq 中通常使用结构体sockaddr来表示socket的地址信息,但是如果需要获得详细的ipv4或者ipv6信息,我们就需要将sockaddr结构体转化为sockaddr_in或者sockaddr_in6。而结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系为:可以参考:
https://blog.csdn.net/albertsh/article/details/80991684
他们的定义为:
/* /usr/include/bits/socket.h */
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
/* /usr/include/netinet/in.h */
/* 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)];
};
/* /usr/include/netinet/in.h */
#ifndef __USE_KERNEL_IPV6_DEFS
/* Ditto, for IPv6. */
struct sockaddr_in6
{
__SOCKADDR_COMMON (sin6_);
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
#endif /* !__USE_KERNEL_IPV6_DEFS */
在sockaddr_in中ipv4的地址存放在sin_addr中,端口号存放在sin_port中。而对于ipv6 ,sockaddr_in6的sin6_addr存放地址,sin6_port存放端口号。所以主要的区别就是sin_addr和sin6_addr以及sin_port和sin6_port的区别
而查看代码发现sin_addr和sin_port的定义为:
/* Type to represent a port. */
typedef uint16_t in_port_t;
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
而sin6_addr和sin6_port的定义为:
#ifndef __USE_KERNEL_IPV6_DEFS
/* IPv6 address */
struct in6_addr
{
union
{
uint8_t __u6_addr8[16];
#if defined __USE_MISC || defined __USE_GNU
uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
#endif
} __in6_u;
#define s6_addr __in6_u.__u6_addr8
#if defined __USE_MISC || defined __USE_GNU
# define s6_addr16 __in6_u.__u6_addr16
# define s6_addr32 __in6_u.__u6_addr32
#endif
};
#endif /* !__USE_KERNEL_IPV6_DEFS */
从上面的定义可以看出,ipv4的ip地址存放在32位无符号整形中,而ipv6的地址时可以变化的,可以将他们存放到不同的容器中。所以也就可以通过不同的方式读出来了。
因此对于同样的IP地址,我们可以使用不同的方式写出,测试代码为:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main() {
struct in6_addr ip;
char *addr1, *addr2, *addr3, *addr4, *addr5;
char ipv6_str[128];
addr1 = strdup("2a01:198:603:0:396e:4789:8e99:890f");
inet_pton(AF_INET6, addr1, &ip);
printf("2a01:198:603:0:396e:4789:8e99:890f \n");
printf("0x%4x %4x %4x %4x %4x %4x %4x %4x\n",htons(ip.s6_addr16[0]),htons(ip.s6_addr16[1]),htons(ip.s6_addr16[2]),htons(ip.s6_addr16[3]),htons(ip.s6_addr16[4]),htons(ip.s6_addr16[5]),htons(ip.s6_addr16[6]),htons(ip.s6_addr16[7]));
printf("xiang %8x %8x %8x %8x \n",htonl(ip.s6_addr32[0]),htonl(ip.s6_addr32[1]),htonl(ip.s6_addr32[2]),htonl(ip.s6_addr32[3]));
return 0;
}
运行结果为:
地址转换函数inet_addr(), inet_aton(), inet_ntoa()和inet_ntop(), inet_pton():
在上面的代码中使用了inet_pton函数来将字符串形式的IP地址转化为数字类型的,而同时也可以使用inet_ntop函数将数字类型的IP地址转化为字符串类型,下面介绍地址转换函数inet_addr(), inet_aton(), inet_ntoa()和inet_ntop(), inet_pton()
参考:http://haoyuanliu.github.io/2017/01/15/%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2%E5%87%BD%E6%95%B0inet-addr-inet-aton-inet-ntoa-%E5%92%8Cinet-ntop-inet-pton/
inet_addr()函数
功能:inet_addr()函数用于将点分十进制IP地址转换成网络字节序IP地址;
原型:in_addr_t inet_addr(const char *cp);
返回值:如果正确执行将返回一个无符号长整数型数。如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE;
头文件:arpa/inet.h (Linux)
inet_aton()函数
功能:inet_aton()函数用于将点分十进制IP地址转换成网络字节序IP地址;
原型:int inet_aton(const char *string, struct in_addr *addr);
返回值:如果这个函数成功,函数的返回值非零,如果输入地址不正确则会返回零;
头文件:sys/socket.h (Linux)
inet_ntoa()函数
功能inet_ntoa()函数用于网络字节序IP转化点分十进制IP;
原型:char *inet_ntoa (struct in_addr);
返回值:若无错误发生,inet_ntoa()返回一个字符指针。否则的话,返回NULL。其中的数据应在下一个WINDOWS套接口调用前复制出来;
头文件:arpa/inet.h (Linux)
inet_ntop()和inet_pton()函数
inet_ntop()函数
功能:inet_ntop()函数用于将网络字节序的二进制地址转换成文本字符串;
原型:const char *inet_pton(int domain, const void *restrict addr, char *restrict str, socklen_t size);
返回值:若成功,返回地址字符串指针;若出错,返回NULL;
头文件:arpa/inet.h (Linux)
inet_pton()函数
功能:inet_pton()函数用于将文本字符串格式转换成网络字节序二进制地址;
原型:int inet_pton(int domain, const char *restrict str, void *restrict addr);
返回值:若成功,返回1;若格式无效,返回0;若出错,返回-1;
头文件:arpa/inet.h (Linux)
在下面的例子中列举了inet_ntop()和inet_pton()的转化:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main() {
struct in6_addr ip;
char *addr1, *addr2, *addr3, *addr4, *addr5;
char ipv6_str[128];
addr1 = strdup("2a01:198:603:0:396e:4789:8e99:890f");
addr2 = strdup("2a01:198:603:0::");
addr3 = strdup("2a01::");
addr4 = strdup("2a01::2a01");
addr5 = strdup("::2a01");
inet_pton(AF_INET6, addr1, &ip);
printf("2a01:198:603:0:396e:4789:8e99:890f \n");
printf("0x%4x %4x %4x %4x %4x %4x %4x %4x\n",htons(ip.s6_addr16[0]),htons(ip.s6_addr16[1]),htons(ip.s6_addr16[2]),htons(ip.s6_addr16[3]),htons(ip.s6_addr16[4]),htons(ip.s6_addr16[5]),htons(ip.s6_addr16[6]),htons(ip.s6_addr16[7]));
printf("xiang %8x %8x %8x %8x \n",htonl(ip.s6_addr32[0]),htonl(ip.s6_addr32[1]),htonl(ip.s6_addr32[2]),htonl(ip.s6_addr32[3]));
printf("0x%4x %4x %4x %4x %4x %4x %4x %4x\n",ip.s6_addr16[0],ip.s6_addr16[1],(ip.s6_addr16[2]),(ip.s6_addr16[3]),(ip.s6_addr16[4]),(ip.s6_addr16[5]),(ip.s6_addr16[6]),(ip.s6_addr16[7]));
int i = 0;
unsigned int ipv6_in32[4] = {0};
unsigned int ipv6_in32_htonl[4] = {0};
for (i=0;i<4;i++) {
printf("333 0x%8x\n",ip.s6_addr32[i]);
ipv6_in32[i] = ip.s6_addr32[i];
}
struct in6_addr ipv6_s_32;
i = 0;
for (i=0;i<4;i++) {
ipv6_s_32.s6_addr32[i] = 0;
ipv6_in32_htonl[i] = htonl(ip.s6_addr32[i]) & 0xffffffff;
printf("000 0x%16x\n",htonl(ip.s6_addr32[i]) & 0xffffffff );
ipv6_s_32.s6_addr32[i] |= ((unsigned int)ip.s6_addr32[i]) & 0xffffffff;
printf("000_ntohl 0x%16x\n",ntohl(ipv6_in32_htonl[i]) & 0xffffffff );
}
if(inet_ntop(AF_INET6,&ipv6_s_32,ipv6_str,40)==NULL) /*地址由二进制数转换为点分十进制*/
{
printf("fail to convert");
}
printf("ipv6_str %s \n",ipv6_str);
return 0;
}
运行结果为:
主机字节序与网络字节序的转化:
通过上面对于htonl和ntohl运行结果的不同可以知道htonl和ntohl的作用是32位的主机字节序与网络字节序的转化:
参考:https://blog.csdn.net/zhangdawei5A504/article/details/45747937
由于不同的系统会有不同的模式,为了统一,规定在网络传输中使用大端模式,这就是网络字节序。而当我们的主机字节与网络字节有所不用的时候就需要使用下面的函数进行转化了:
uint32_t htonl(uint32_t hostlong);//32位的主机字节序转换到网络字节序
uint16_t htons(uint16_t hostshort);//16位的主机字节序转换到网络字节序
uint32_t ntohl(uint32_t netlong);//32位的网络字节序转换到主机字节序
uint16_t ntohs(uint16_t netshort);//16位的网络字节序转换到主机字节序
拿htonl和ntohl来分析,htonl函数的内部实现原理是这样,先判断主机是什么模式存储,如果是大端模式,就跟网络字节序一致,直接返回参数即可,如果是小端模式,则把形参转换成大端模式存储在一个临时参数内,再把临时参数返回;而ntohl函数的实现原理也是一样的过程,但是要注意它的参数,参数是网络字节序,就是大端模式存储,而不管你传入实参的过程是如果存储的,因此当判断主机是大端模式的时候,会直接返回,因为该函数默认会认为形参是网络字节序,把它当大端模式来看,如果判断主机是小端模式,就会将实参做转换,转换的过程并不复杂,就是逆序存储各个字节的数据,所以结果就被转换。