获取本地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函数的实现原理也是一样的过程,但是要注意它的参数,参数是网络字节序,就是大端模式存储,而不管你传入实参的过程是如果存储的,因此当判断主机是大端模式的时候,会直接返回,因为该函数默认会认为形参是网络字节序,把它当大端模式来看,如果判断主机是小端模式,就会将实参做转换,转换的过程并不复杂,就是逆序存储各个字节的数据,所以结果就被转换。

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