聲明:
本文是我在工作中遇到的網絡相關的問題,以及自己的一些總結,希望可以對你有所幫助。
介紹:
獲得本地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函數的實現原理也是一樣的過程,但是要注意它的參數,參數是網絡字節序,就是大端模式存儲,而不管你傳入實參的過程是如果存儲的,因此當判斷主機是大端模式的時候,會直接返回,因爲該函數默認會認爲形參是網絡字節序,把它當大端模式來看,如果判斷主機是小端模式,就會將實參做轉換,轉換的過程並不複雜,就是逆序存儲各個字節的數據,所以結果就被轉換。