获取本地ipv4和ipv6信息

rust获取本地ip地址的方法 get_local_info是一个获取linux系统信息的rust三方库,并提供一些常用功能,目前版本0.2.4。大家好,我是get_local_info作者带剑书生,这里用一篇文章讲解get_local_info的使用。输入ifconfig看到,IP地址正是目前所使用的活动网卡的IPv4和IPv6地址。到这里,你已经获取到了本地的IP地址了,有什么疑问请联系我吧。get_local_info是什么? 阅读详情

声明:

    本文是我在工作中遇到的网络相关的问题,以及自己的一些总结,希望可以对你有所帮助。

介绍:

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

IPv4 和 IPv6 nternetrotocol运行在五层协议的体系结构(Physical Layer物理层、Data Link Layer数据链路层、Network Layer网络层、Transport Layer传输层、Application Layer应用层)中的网络层,它是TCP/IP协议族中最为核心的协议之一,负责为数据包提供无连接的、不可靠的、尽最大努力交付的服务。 阅读详情

相关推荐

IPV4上建立IPV6隧道以及IPV6上建立IPV4隧道

实验目录: 1)在IPV4中穿越IPV6。 2)有IPV6中穿越IPV4。 实验配置: 1)各接口IP配置如上,且在R5、R2、R3、R4的ipv6接口上都配置有ripng路由协议(注意关闭水平分割)。R2、R3都有指向公网的默认路由。 1.1、实验拓扑: 1.2、在IPv4中穿越ipv6,建立隧道如下 R2#sh run interface

oO天龙的博客 2万+

/proc/net/if_inet6文件内容详解

当我们在Linux系统中需要查看接口和地址关系时有两个文件可以查看 /proc/net/if_inet6 //接口和单播地址 /proc/net/igmp6 //接口和组播地址 最开始接触到IPv6的开源代码时看到代码中有去读取/proc/net/if_inet6文件,但是看完之后一脸懵逼,自己去网上搜索后发现关于这个文件内容的介绍是少之又少(实际上关于IPv6的资料网上是少之又少,英文的还有点中文的几乎没得~),所以就想着结合代码和自己的理解来写一个,希望能给刚接触IPv6的人一些帮助

qq_40074603的博客 4885

c++对ipv4和ipv6地址的兼容处理

目前ipv6的应用越来越广泛,慢慢将变成强制的标准,做网络应用开发的,在未来会越来越的要开始支持ipv6,但是ipv6肯定不是一下子就可以大范围应用的,必然有一段时间Ipv4与ipv6共存,要做到对ipv4和ipv6的同时支持。 关于如何让程序支持ipv6可以参考 https://blog.youkuaiyun.com/v6543210/article/details/106927210 示例代码见:https://github.com/alongL/ipv6_demo 这个文章只讲如何兼容ipv4和...

闲人的专栏 4653

Linux系统编程获取指定接口的IPv6地址

在Linux系统中需要编程获取某个接口的IPv4地址比较常见, 可以使用ioctl函数等方法, 但是要获取IPv6地址, 以前的一些方法就不一定适合, 从网上查找了很多方法, 有两种我试过都可以得到相应的IPv6地址, 此处直接以一段代码做示例展示如何获取指定接口的(IPv6)本地链路地址(注意, 前提是你的Linux支持IPv6, 目前流行的大多数Linux系统早已支持IPv6). #in

h_cszc的专栏 1万+

正则匹配ipv4和ipv6,判断ipv4和ipv6的合法性

python写法,正则匹配ipv4和ipv6,并判断其合法性。 import sys import re def is_ipv4(ip): p = re.compile( '^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$') if p.match(ip): ...

果冻先生的专栏 3590

ipv6 over ipv4

ipv6 over ipv4技术:使得ipv6报文在ipv4网络中传输,实现ipv6网络的互联。 实现思路如下:在两个ipv6网络分别运行rip和ospf,在ipv4网络运行ospf,在网络边缘设备AR2和AR4上搭建一条虚拟隧道使得数据能过通过隧道传输在AR1的配置如下:ipv6                                          全局开启ipv6模式ripng 1...

weixin_37945631的博客 6378

获取本地IP网卡信息

获取本地网卡信息,和命令行ipconfig一样

yang20000222的博客 948

IPv4头部和IPv6头部

IPv4头部和IPv6头部

weixin_42831406的博客 2439

C#_获取本地IP地址

using System.Net; void GetLocalIp() { //获取本机名称 string hostname = Dns.GetHostName(); //获取本机DNS信息 IPHostEntry localhost = Dns.GetHostEntry(hostname); /...

Jo.H的博客 570

C语言如何获取ipv6地址

使用通常获取ipv4的IP地址的方法是无法获取ipv6地址的,本文介绍了使用C语言获取ipv6地址的三种方法,每种方法均给出了完整的源程序,本文所有实例在 ubuntu 20.04 下测试通过,gcc 版本 9.4.0。

whowin 6684

centOS-7.x如何配置ipv6

1.场景还原 阿里云服务器默认不支持ipv6模块加载,但是ios App能够成功上线与否,依赖服务器是否支持ipv6访问;今天笔者就linux-7.x如何配置ipv6给大伙讲解下,希望能够帮助到有需要的小伙伴们 2.实现方案

zhangxing 1万+

QT获取IP地址

QT获取本地计算机名、IPV4/6地址、获取了公网地址

断点 666

Linux: proc/net/ipv6_route

【代码】Linux: proc/net/ipv6_route。

mzhan017的博客 1498

Linux 接口地址的获取-暨获取本地IP地址(所有地址,包括IPv4,IPV6,MAC 地址)...

本文没有使用ioctl 函数,未使用socket ,直接获取本地的所有地址,包括IPv4,IPV6,MAC 地址: 核心函数是:getifaddrs() & freeifaddrs() 代码如下: #include <stdio.h> #include <sys/socket.h> #include <ifaddrs.h> #inclu...

weixin_34174322的博客 278

本地IP与机器名获取指南:Windows与Linux/Unix

简介:在IT管理中,获取本地IP地址和机器名对于网络通信和系统管理至关重要。本文将提供在Windows和Linux/Unix系统中获取这些信息的命令行、编程接口以及第三方工具的方法。读者将学习如何使用各种技术手段,包括命令行工具如ipconfighostname以及编程方法如.NET Framework和WinAPI,来获取和理解本地网络配置。

weixin_42298778的博客 1424

Oracle-OGG trail 文件大小引起的进程异常 OGG-01172

监控OGG目标端进程,其中一个进程异常,状态为 ABENDED,start repa 执行失败。进入OGG报告目录检查运行报告 2015-04-20 09:38:36 ERROR OGG-01172 Discard file (/ogg/oggrm/11.2/dirdat/repa.dsc) exceeded max bytes (2000000000). 总结处理方法,从根本上解决问题。

xdyzgjy的专栏 7956

什么是ipv4,什么是ipv6,ipv4和ipv6的主要区别有哪些?

IPv6(Internet Protocol Version 6)是为了解决IPv4地址枯竭而提出的新一代互联网协议。IPv6使用128位地址,可以生成约3.4 x 10^38个唯一地址,因此理论上能够满足全球网络需求。IPv6还包含了多项改进,包括更强的安全性和更高的效率。IPv6地址示例IPv6地址采用冒号分隔的十六进制表示方法,通常包含8个由冒号分隔的16位字段。IPv4和IPv6都是网络通信的核心协议,但IPv6在地址空间和安全性方面有明显优势。

weixin_66419566的博客 1万+

S型速度曲线C程序实现

S型速度曲线C语言程序实现 前言 S型曲线相较于梯形速度曲线具有运行更加平稳的特点,S型曲线共有八种运行情况,感兴趣可以查阅相关文献 代码段 #include <stdio.h> #include <math.h> #define FP64 double int main(void) { /**************************** 定义相关变量(需要映射到PLC寄存器才能进行监控) ***********************/ FP6

LSX312441548的博客 8392

ipv6和ipv4的过渡方案

ipv6和ipv4的过渡方案(隧道技术:6 to 4 ,6 over 4,isatap,mpls,双协议栈以及翻译技术)

weixin_69884785的博客 7767

【实战教程】OpenClaw接入MySQL数据库实战指南

本文介绍了在虚拟机Ubuntu中访问宿主机Win11中MySQL数据库的配置方法,重点包括创建远程用户、权限配置和安全设置。首先在MySQL中创建具有只读权限的用户并刷新权限,然后调整服务器设置允许远程连接。文章还提供了某金融机构财富管理系统的数据库设计方案,包含理财产品表、客户表和份额表的核心结构,其中金额字段使用DECIMAL类型确保精度,并设置唯一键约束。文末强调测试环境配置完成后应恢复安全更新模式以保障生产环境安全。

海兰 8696

【HEC】HECRAS中的降雨边界

网格降雨可能是城市地区建模的一个好选择。这是因为传统的一维(1D)建模并不能完全捕捉城市地区通常出现的复杂流动模式。使用网格降雨建模方法在构建包含多个未定义流动路径的模型时也非常有用。对于流量分支非常复杂的水道,设定边界条件可能会很困难。 HECRAS提供两种降雨边界添加的方法 - 在指定SA/2D FLOW AREA添加降雨边界 - 在hec-ras 6.0以上版本中 “气象数据” 的选项卡,可以添加具有空间变化的降水量、蒸散量和风。 两种方法只能选择一种,以下分别介绍

```On Earth, Be Water, By Coding``` 1398
上一篇: C语言结构体赋值
下一篇: 手机投屏方式
moxue10
博客等级 码龄19年 388粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值