注意:结构体之间不能直接进行强制转换, 必须先转换成指针类型才可以进行结构体间的类型转换, 这里需要明确的定义就是什么才叫强制转换.
强制转换是将内存中一段代码以另一种不同类型的方式进行解读, 因此转换的空间必须与源空间一一对应.
而结构体则是由不固定的多种类型变量组合而成, 因此强制转换时并不确定原格式与目标格式确定的对应关系, 例如一个结构体为3个变量, 而另一个则为2个, 那么就无法确定如何分配.
因此最简单的让计算机可以理解的方式就是先将结构体转换成指针, 然后将指针转换成目标格式, 再读取转换后的指针, 这样计算机就会按照指针的格式读取特定目标代码段了.
1、sockaddr 、sockaddr_in 、addrinfo比较
struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
sa_family是地址家族,一般都是“AF_xxx”的形式。好像通常大多用的是都是AF_INET。
sa_data是14字节协议地址。
此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in,在netinet/in.h中定义:
struct sockaddr_in { short int sin_family; /* Address family */ unsigned short int sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ unsigned char sin_zero[8]; /* Same size as struct sockaddr */ }; struct in_addr { unsigned long s_addr; }; typedef struct in_addr { union { struct { unsigned char s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { unsigned short s_w1, s_w2; } S_un_w; unsigned long S_addr; } S_un; } IN_ADDR;sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序)
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
s_addr按照网络字节顺序存储IP地址。
addrinfo结构是getaddrinfo函数用来保存主机地址信息的结构体。在头文件Ws2tcpip.h中定义。
typedef struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; char* ai_canonname; struct sockaddr* ai_addr; struct addrinfo* ai_next; } ADDRINFOA, *PADDRINFOA;
ai_flags:表示getaddrinfo要使用的选项,可赋的值在Winsock2.h中有定义,且可以是几个值的组合。
ai_family:地址族。一般为AF_INET。
ai_socktype:套接字类型。SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,SOCK_RDM,SOCK_SEQPACKET。
ai_protocol:协议类型。可取的值取决于ai_address和ai_socktype的值。IPPROTO_TCP,IPPROTO_UDP,IPPROTO_RM。
如果ai_family置为AF_IRDA,则ai_protocol必须为0。
ai_addrlen:ai_addr指向的缓冲区的字节数。
ai_canonname:主机的规范化名称(canonical name)。
ai_addr:指向sockaddr结构的指针。
2、IP地址的网络字节顺序和机器字节顺序
210.25.132.181属于IP地址的ASCII表示法,也就是字符串形式。英语叫做IPv4 numbers-and-dots notation。
如果把210.25.132.181转换为整数形式,是3524887733,这个就是整数形式的IP地址。英语叫做binary data。(其实binary是二进制的意思)
3、ntohs, ntohl, htons,htonl的比较和详解
ip地址是32位的,端口号是16位的 。
ntohs =net to host short int 16位
htons=host to net short int 16位
ntohs =net to host long int 32位
htonl=host to net long int 32位
u_short PASCAL FAR ntohs( u_short netshort);
netshort:一个以网络字节顺序表达的16位数。本函数将一个16位数由网络字节顺序转换为16位主机字节顺序。
u_short PASCAL FAR htons( u_short hostshort);hostshort:一个主机字节顺序表达的16位数。 本函数将一个16位数从主机字节顺序转换成16位网络字节顺序。
u_long PASCAL FAR htonl( u_long hostlong);
hostlong:一个主机字节顺序表达的32位数。本函数将一个32位数从主机字节顺序转换成32位网络字节顺序。
u_long PASCAL FAR ntohl( u_long netlong);
netlong:一个以网络字节顺序表达的32位数。 本函数将一个32位数由网络字节顺序转换为32位的主机字节顺序。
4、 inet_network()、inet_addr()、inet_aton()、inet_ntoa()
int inet_aton(const char *string, struct in_addr *addr);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char FAR* PASCAL FAR inet_ntoa( struct in_addr in);
函数inet_addr():将IP地址从 点数格式转换成无符号长整型。使用方法如下:
ina.sin_addr.s_addr = inet_addr("132.241.5.10");注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。
函数inet_network():inet_network和inet_addr函数都是用于将字符串形式转换为整数形式用的,两者区别很小,inet_addr返回的整数形式是网络字节序,而inet_network返回的整数形式是主机字节序。
函数int inet_aton(const char *string, struct in_addr *addr):是一个改进的方法来将一个字符串IP地址转换为一个32位的网络序列IP地址。输入参数string包含ASCII表示的IP地址。 输出参数addr是将要用新的IP地址更新的结构。 返回值: 如果这个函数成功,函数的返回值非零。如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以他的值会被忽略。 inet_aton函数和上面这俩小子的区别就是在于他认为255.255.255.255是有效的,他不会冤枉这个看似特殊的IP地址。inet_aton函数返回的是网络字节序的IP地址。如:inet_aton("127.0.0.1",&adr_inet.sin_addr))
函数inet_ntoa():将网络地址转换成“.”点隔的字符串格式。本函数将一个用in参数所表示的Internet地址结构转换成以“.” 间隔的诸如“a.b.c.d”的字符串形式。请注意inet_ntoa()返回的字符串存放在WINDOWS套接口实现所分配的内存中。应用程序不应假设该内存是如何分配的。在同一个线程的下一个WINDOWS套接口调用前,数据将保证是有效。返回值: 若无错误发生,inet_ntoa()返回一个字符指针。否则的话,返回NULL。其中的数据应在下一个WINDOWS套接口调用前复制出来。函数 inet_ntoa()("ntoa"的含义是"network to ascii"),就像这样: printf("%s",inet_ntoa(ina.sin_addr)); 它将输出IP地址。
5、inet_pton() 和inet_ntop()
Linux下这2个IP地址转换函数,可以在将IP地址在“点分十进制”和“整数”之间转换 而且,inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。算是比较新的函数了。
inet_pton函数原型如下[将“点分十进制” -> “整数”] #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> int inet_pton(int af, const char *src, void *dst); 这个函数转换字符串到网络地址,第一个参数af是地址族,转换后存在dst中 。
inet_pton 是inet_addr的扩展,支持的多地址族有下列: af = AF_INET src为指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函数将该地址 转换为in_addr的结构体,并复制在*dst中 af =AF_INET6 src为指向IPV6的地址,,函数将该地址 转换为in6_addr的结构体,并复制在*dst中 如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。
inet_ntop函数原型如下[将“点分十进制” -> “整数”] #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); 这个函数转换网络二进制结构到ASCII类型的地址,参数的作用和上面相同,只是多了一个参数socklen_t cnt,他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。
6、gethostbyname和gethostbyaddr
这两个函数仅仅支持IPv4,getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。这些sockaddr结构随后可由套接口函数直接使用。如此以来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。应用程序只要处理由getaddrinfo函数填写的套接口地址结构。该函数在 POSIX规范中定义了。
#include<netdb.h>
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
返回0: 成功
返回非0: 出错
hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
service:一个服务名或者10进制端口号数串。
hints:可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
本函数通过result指针参数返回一个指向addrinfo结构链表的指针,而addrinfo结构定义在头文件netdb.h中:
struct addrinfo{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
char *ai_canonname;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
};
如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以导致返回多个addrinfo结构的情形有以下2个:
1. 如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
2. 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。
我们必须先分配一个hints结构,把它清零后填写需要的字段,再调用getaddrinfo然后遍历一个链表逐个尝试每个返回地址。
getaddrinfo解决了把主机名和服务名转换成套接口地址结构的问题。
其中,如果getaddrinfo出错,那么返回一个非0的错误值。