獲取本地ipv4和ipv6信息

聲明:

    本文是我在工作中遇到的網絡相關的問題,以及自己的一些總結,希望可以對你有所幫助。

介紹:

    獲得本地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函數的實現原理也是一樣的過程,但是要注意它的參數,參數是網絡字節序,就是大端模式存儲,而不管你傳入實參的過程是如果存儲的,因此當判斷主機是大端模式的時候,會直接返回,因爲該函數默認會認爲形參是網絡字節序,把它當大端模式來看,如果判斷主機是小端模式,就會將實參做轉換,轉換的過程並不複雜,就是逆序存儲各個字節的數據,所以結果就被轉換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章