zoukankan      html  css  js  c++  java
  • IP地址结构信息与字符串相互转化:inet_pton和inet_ntop, etc.

    1. 问题描述

    1)假如有一个IPv4地址字符串(点分十进制),如何转化为socket编程所需的sockaddr地址结构信息?
    2)假如通过getaddrinfo等方式,已经取得了sockaddr地址结构信息,如何转化为字符串形式?(点分十进制 for IPv4, 冒号十六进制 for IPv6)

    什么是IP地址文本形式?
    点分十进制数串: 形如"192.168.0.1"
    冒号十六进制数串:形如"fe80::20c:29ff:fe99:de11"


    2. IPv4场景解决方案

    inet_aton, inet_addr, inet_ntoa在点分十进制数串与长度为32bits的网络字节序二进制值间转换IPv4地址。 不适用于IPv6地址。

    2.1 inet_aton函数

    点分十进制数串 -> IPv4地址结构(in_addr)
    将主机地址cp(点分十进制数串)转换成一个32bits的网络字节序二进制值,保存到inp指向的缓存。

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int inet_aton(const char *cp, struct in_addr *inp);
    
    • 参数
      cp 点分十进制数串,由调用者提供, 通常cp采用形如a.b.c.d形式, 其他支持的形式参见man inet_aton手册
      inp 输出参数, 指向in_addr结构。由调用者维护缓存, 函数填写内容

    • 返回值
      若cp指向的地址有效,返回非0;若无效,返回0

    特别地,

    如果inp为NULL,那么该函数仍然将输入的字符串执行有效性检查,但是不存储任何结果

    2.2 inet_addr函数

    点分十进制数串 -> IPv4地址结构(in_addr)
    inet_addr同inet_aton的区别是, inet_addr返回32bits的网络字节序二进制值

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    in_addr_t inet_addr(const char *cp);
    

    该函数有个特殊情况,

    所有232个可能值都是有效IP地址(0.0.0.0 到 255.255.255.255),但是出错时,函数返回INADDR_NONE(通常-1,对应32个全1的值),也就是说点分十进制数串255.255.255.255(IPv4有限广播地址)不能由该函数处理。

    因此,应当避免使用inet_addr

    2.3 inet_ntoa函数

    IPv4地址结构(in_addr) -> 点分十进制数串
    函数将32bits主机地址in转化成点分十进制数串

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    char *inet_ntoa(struct in_addr in);
    
    • 参数
      in 调用者传入,IPv4地址结构对象。注意到与inet_aton的参数inp的区别,in并非指针

    • 返回值
      返回static分配的缓存, 后续的调用会覆写之前的内容。因此,如果不想被覆盖,需要自行分配存储进行复制、保存。

    2.4 in_addr和sockaddr_in地址结构

    可以从下面看到,in_addr是sockaddr_in的一个成员,而in_addr实际上是一个32bit的无符号整型,不过被包装成的结构体。
    这也就是说, 如果是想直接将点分十进制数串和ip地址信息相互转换时, 应该是与sockaddr_in.sin_addr进行转化,而不能直接与sockaddr_in对象或s_addr进行转换。

    sockaddr_in结构:

    /* 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)];
    };
    
    #define	__SOCKADDR_COMMON(sa_prefix) 
      sa_family_t sa_prefix##family
    #define __SOCKADDR_COMMON_SIZE	(sizeof (unsigned short int))
    

    in_addr结构:

    typedef uint32_t in_addr_t;
    
    struct in_addr {
       in_addr_t s_addr;
    };
    

    2.4 示例

    通过inet_aton,inet_addr,将指定点分十进制ip文本串转化为二进制数;然后,通过inet_ntoa将指定ip地址二进制数转化为文本串,观察是否能还原文本串。

    int test()
    {
    	char *ipstr = "192.168.0.1";
    //	char *ipstr = "255.255.255.255";
    	struct sockaddr_in sockaddr;
    	int ret;
    	char *str;
    
    	/* convert ip from text to binary for IPv4  */
    	ret = inet_aton(ipstr, &sockaddr.sin_addr);
    	if (ret == 0) {
    		fprintf(stderr, "inet_aton error: invalid ip string: %s
    ", ipstr);
    		return -1;
    	}
    	printf("inet_aton convert ip : from %s to %d
    ", ipstr, sockaddr.sin_addr.s_addr);
    
    	/* convert ip from text to binary for IPv4 except 255.255.255.255 */
    	sockaddr.sin_addr.s_addr = inet_addr(ipstr); /* avoid to use inet_addr */
    	if (sockaddr.sin_addr.s_addr == INADDR_NONE) {
    		fprintf(stderr, "inet_addr error: invalid ip string: %s
    ", ipstr);
    				return -1;
    	}
    	printf("inet_addr convert ip : from %s to %d
    ", ipstr, sockaddr.sin_addr.s_addr);
    
    	/* convert ip from binary to text for IPv4 */
    	str = inet_ntoa(sockaddr.sin_addr);
    	if (str == NULL) {
    		fprintf(stderr, "inet_ntoa error: invalid ip structure: %d
    ", sockaddr.sin_addr.s_addr);
    		return -1;
    	}
    	printf("inet_ntoa convert ip: from %d to %s
    ", sockaddr.sin_addr.s_addr, str);
    
    	return 0;
    }
    

    运行结果:

    inet_aton convert ip : from 192.168.0.1 to 16820416
    inet_addr convert ip : from 192.168.0.1 to 16820416
    inet_ntoa convert ip: from 16820416 to 192.168.0.1
    

    3. IPv4,IPv6场景共同解决方案

    3.1 inet_pton函数

    IPv4和IPv6地址: 文本 -> 二进制
    将地址文本串 转换成 网络字节序二进制数形式

    #include <arpa/inet.h>
    
    int inet_pton(int af, const char *src, void *dst);
    
    • 参数
      af 地址家族,支持的取值(且只能是这2个):
      1)AF_INET 适用于IPv4地址,只支持"ddd.ddd.ddd.ddd"这种点分十进制数串
      2)AF_INET6 适用于IPv6地址
      注意:这里不同于socket()的af(地址family/domain),没有AF_UNIX

    src 指向IP地址的文本形式的字符串(点分十进制数串 for IPv4,冒号十六进制数串 for IPv6)

    dst 指向网络地址结构(struct in_addr for IPv4, struct struct in_addr6 for IPv6)

    • 返回值
      成功,返回1(网络地址成功转换);如果在指定地址家族中,src不是一个有效网络地址文本串,则返回0;如果af不包含一个有效地址家族(非规定2个取值),则返回-1, 且errno设置为EAFNOSUPPORT。

    3.2 inet_ntop函数

    IPv4和IPv6地址: 二进制 -> 文本
    将地址网络字节序二进制数 转换成 文本串形式

    #include <arpa/inet.h>
    
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    
    • 参数
      af 地址家族,支持的取值(且只能是这2个):
      1)AF_INET 适用于IPv4地址,只支持"ddd.ddd.ddd.ddd"这种点分十进制数串
      2)AF_INET6 适用于IPv6地址
      注意:这里不同于socket()的af(地址family/domain),没有AF_UNIX

    src 指向二进制地址结构,对于ipv4, src指向struct in_addr;对于ipv6,src指向struct in_addr6

    dst 指向存放地址文本串的缓存,由调用者维护缓存, 函数填充内容

    size 指示dst指向缓存大小

    • 返回值
      成功,返回指向dst的非NULL指针;失败, 返回NULL,errno被设置

    3.3 示例

    对于ipv4,方式同inet_aton, inet_addr, inet_ntoa,先将文本串转化为二进制数,打印;然后再转化为文本串,再打印。观察中间输出,以及最终是否能还原。
    对于ipv6,因为ipv6地址二进制数128bit, 这里没有直接打印二进制数,而是先将已知的ipv6文本串(冒号十六进制表示法)先通过inet_pton转化为二进制串,再通过inet_ntop转换回文本串。观察是否能还原。

    int test() 
    {
            /* ipv4 solution */
            struct sockaddr_in sockaddr;
            char *ipstr = "192.168.0.1";
            char ip4text[INET_ADDRSTRLEN];
            char *str;
            int ret;
    
    	printf("===ipv4===
    ");
    
    	/* convert ip from text to binary for IPv4  */
    	bzero(&sockaddr, sizeof (sockaddr));
    	ret = inet_pton(AF_INET, ipstr, &sockaddr.sin_addr);
    	if (ret != 1) {
    		if (ret == 0) {
    			fprintf(stderr, "inet_pton error: %s is not a valid ip text
    ", ipstr);
    		}
    		else if (ret == -1) {
    			perror("inet_pton error");
    		}
    
    		fprintf(stderr, "inet_pton error: unkown error");
    		return -1;
    	}
    	printf("inet_pton convert ip from %s to %d
    ", ipstr, sockaddr.sin_addr.s_addr);
    
    	/* convert ip from binary to text  for IPv4  */
    	str = inet_ntop(AF_INET, &sockaddr.sin_addr, ip4text, sizeof(ip4text));
    	if (str == NULL) {
    		perror("inet_ntop error");
    		return -1;
    	}
    	printf("inet_ntop convert ip from %d to %s
    ", sockaddr.sin_addr.s_addr, str);
    
            /* ipv6 solution */
    	char *ip6str = "fe80::20c:29ff:fe99:de11"; /* inet6 from bash "$ ifconfig -a" */
    	char ip6text[INET6_ADDRSTRLEN];
    	struct sockaddr_in6 sockaddr6;
    
    	printf("===ipv6===
    ");
    	/* convert ip from text to binary for IPv6  */
    	bzero(&sockaddr6, sizeof (sockaddr6));
    	ret = inet_pton(AF_INET6, ip6str, &sockaddr6.sin6_addr);
    	if (ret != 1) {
    		if (ret == 0) {
    			fprintf(stderr, "inet_pton error: %s is not a valid ipv6 text
    ", ip6str);
    		}
    		else if (ret == -1) {
    			perror("inet_pton error");
    		}
    
    		fprintf(stderr, "inet_pton error: unkown error");
    		return -1;
    	}
    
    	/* convert ip from binary to text  for IPv6  */
    	str = inet_ntop(AF_INET6, &sockaddr6.sin6_addr, ip6text, sizeof(ip6text));
    	if (str == NULL) {
    		perror("inet_ntop error");
    		return -1;
    	}
    	printf("%s
    ", str);
    }
    

    运行结果:

    ===ipv4===
    inet_pton convert ip from 192.168.0.1 to 16820416
    inet_ntop convert ip from 16820416 to 192.168.0.1
    ===ipv6===
    fe80::20c:29ff:fe99:de11
    

    3.4 sock_ntop自定义函数

    inet_ntop存在一个问题:要求调用者传递一个指向某个二进制地址的指针src,而该指针通常包含在一个套接字地址结构中,也就是说,调用者必须直到这个结构的格式,地址家族,以及应该具体为哪个IP格式提供各自的地址结构(struct sockaddr_in for IPv4, struct sockaddr_in6 for IPv6)

    i.e. 对IPv4,应该编写这样的代码:

    struct sockaddr_in sockaddr;
    inet_ntop(AF_INET, &sockaddr.sin_addr, str, sizeof(str));
    

    对IPv6,应该编写这样的代码:

    struct sockaddr_in6 sockaddr6;
    inet_ntop(AF_INET6, &sockaddr6.sin_addr, str, sizeof(str)); 
    

    这样,就使得代码的编写与协议相关。为解决该问题,编写sock_ntop,以指向某个套接字地址结构的指针为参数,查看该结构的内部,然后根据具体情况返回地址的文本串格式

    char *sock_ntop(const struct sockaddr *sa, socklen_t addrlen);
    

    函数实现

    char *sock_ntop(const struct sockaddr *sa, socklen_t addrlen)
    {
    	char portstr[8];
    	static char str[128]; /* struct sockaddr_storage最多128byte */
    
    	switch (sa->sa_family) {
    	case AF_INET: {
    		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
    		if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
    			return NULL; /* 转换存在错误 */
    		if (ntohs(sin->sin_port) != 0) {
    			snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
    			strcat(str, portstr);
    		}
    		return str;
    	}
    
    	case AF_INET6: {
    		struct sockaddr_in *sin6 = (struct sockaddr_in *)sa;
    		if (inet_ntop(AF_INET6, &sin6->sin_addr, str, sizeof(str)) == NULL)
    			return NULL; /* 转换存在错误 */
    		if (ntohs(sin6->sin_port) != 0) {
    			snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin6->sin_port));
    			strcat(str, portstr);
    		}
    		return str;
    	}
    
    	default:
    		errno = EAFNOSUPPORT; /* 不支持的地址family */
    	}
    	return NULL;
    }
    
  • 相关阅读:
    再次尝试windows下msys+MinGW编译ffmpeg
    iOS设备的硬件适配 (关于armv6, armv7, armv7s ) <转>
    vmware虚拟机下ubuntu 13.04使用zeranoe脚本交叉编译ffmpeg
    vmware 8下ubuntu 13.04安装vmware tools
    ubuntu下使用脚本交叉编译windows下使用的ffmpeg + X264
    网上看来的
    ffmpeg关于aac解码
    iOS阶段学习第15天笔记(NSDate操作)
    iOS阶段学习第15天笔记(NSDictionary与NSMutableDictionary 字典)
    iOS阶段学习第15天笔记(NSArray与NSMutableArray 数组)
  • 原文地址:https://www.cnblogs.com/fortunely/p/14916296.html
Copyright © 2011-2022 走看看