引言:套接字地址结构在网络编程的每个实现中都要用到,因此掌握套接字地址结构是以后编写网络程序的前提,地址结构可以在两个方向上传递:从进程到内核和从内核到进程。地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换。
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。不同协议都有自己的套接字地址结构。通用的套接字地址结构是sockaddr。IPv4套接字地址结构是定义在头文件<netinet/in.h>中的sockaddr_in,其POSIX定义如下:
struct in_addr{ in_addr_t s_addr; /*32-bit IPv4 address*/ }; /*network byte ordered*/ struct sockaddr_in{ unit8_t sin_len; /*length of structure(16)*/ sa_family_t sin_family; /*AF_INET*/ in_port_t sin_port; /*16-bit TCP or UDP port number*/ /*network byte ordered*/ struct in_addr sin_addr; /*32-bit IPv4 address, network byte ordered*/ char sin_zero[8]; };POSIX规范只需要结构中的3个字段:sin_family、sin_addr和sin_port。它们的数据类型也都在上面给出了。
注意:32位IPv4地址存在两种不同的访问方法。举例来说,如果serv定义为某个网络套接字地址结构,那么serv.sin_addr将按in_addr结构引用其中的32位IPv4地址,而serv.sin_addr.s_addr将按in_addr_t(通常是一个无符号的32位整数)引用同一个32位IPv4地址。因此,必须正确使用IPV4地址,尤其是将它作为函数的参数时,因为编译器对传递结构和传递整数的处理时完全不同的。
通用套接字地址结构sockaddr定义在<sys/socket.h>头文件中。
struct sockaddr{ unit8_t sa_len; /*address family:AF_xxx value*/ sa_family_t sa_family; /*protocol-specific address*/ };套接字函数被定义为以指向通用套接字地址结构的指针作为参数,如int bind(int, struct sockaddr *, socklen_t);因此,调用这些函数时必须将指向特定于协议的套接字地址结构的指针进行强制类型转换,变成指向某个通用套接字地址结构的指针。如struct sockaddr_in serv; bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));该技巧几乎用在了所有套接字函数中,一定要熟练掌握。
套接字地址结构有5种:IPv4、IPv6、Unix域、数据链路和存储。
地址转换函数,在ASCII字符串与网络字节序的二进制值之间转换网际地址。inet_aton、inet_addr(已经不用了)和inet_ntoa在点分十进制数串(例如“206.168.112.96”)与它长度为32位的网络字节序二进制值间转换IPv4地址。而inet_pton和inet_ntop对于IPv4和IPv6地址都适用。
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); /*返回:若字符串有效则为1,否则为0*/ char *inet_ntoa(struct in_addr inaddr); /*返回:指向一个点分十进制数串的指针*/inet_aton将strptr所指字符串转换为一个32位的网络字节序二进制值,并通过指针addrptr来存储。若成功为1,否则为0。inet_ntoa函数将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串,由该函数的返回值所指向的字符串驻留在静态内存中。注意,该函数以一个结构而不是该结构的指针作为其参数。
#include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr); const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
两个函数的family参数既可以是AF_INET,也可以是AF_INET6。如果以不被支持的地址族作为family参数,这两个函数就返回一个错误,并将errno置位EAFNOSUPPORT。
第一个函数尝试转换由strptr指针所指的字符串,并通过addrptr指针存放二进制结果。若成功则返回值为1,否则如果对所指定的family而言输入的字符串不是有效的表达格式,返回0。inet_ntop进行相反的转换,从数值格式转换为表达式。
既然上面提到了网络字节序,那么就要说一说网络字节序与主机字节序的区别。
内存中存储两个字节有两种方法:一种是将迪许字节存储在起始地址,称为小端字节序;另一种是将高端字节存储在起始地址,称为大端字节序。
主机字节序是系统所用的字节序,可以用下列程序测试:
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { union{ short s; char c[sizeof(short)]; }un; un.s = 0x0102; if(sizeof(short) == 2){ if(un.c[0] == 1 && un.c[1] == 2) printf("big-endian\n"); else if(un.c[0] == 2 && un.c[1] == 1) printf("little-endian\n"); else printf("unkonwn\n"); }else printf("sizeof(short) = %d\n", sizeof(short)); exit(0); }网络字节序和主机字节序之间相互转换的函数如下:
#include <netinet/in.h>
unit16_t htons(unit16_t host16bitvalue);
unit32_t htons(unit32_t host32bitvalue);/*返回网络字节序的值*/
unit16_t ntohs(unit16_t net16bitvalue);
unit32_t ntohs(unit32_t net32bitvalue); /*返回主机字节序的值*/
应该把s视为一个16位的值(例如TCP或UDP端口号),把l视为一个32位的值(例如IPv4地址);
除了协议首部中各个字段的字节序问题外,还有网络分组中所含数据的字节序问题。
补充:字节操纵函数
操纵多字节字段的函数有两组:它们既不对数据做解释,也不假设数据是以空字符结束的字符串。
#include <string.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t bytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
#include <string.h>
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);