大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义了它自己的套接字地址结构。这些结构的名字均以sockaddr_开头,并以对应每个协议族的唯一后缀结尾。
1、IPv4套接字地址结构
IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinet/in.h>头文件中。图1给出了它的POSIX定义:
struct in_addr {
in_addr_t s_addr; /* 32-bit IPv4 address */
/* network byte ordered */
};
struct sockaddr_in {
uint8_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]; /* unused */
};
图1 网际(IPv4)套接字地址结构:sockaddr_in
利用图1所示的例子,我们对套接字地址结构做几点一般性的说明:
•长度字段sin_len是为增加对OSI协议的支持而随4.3BSD-Reno添加的。在此之前,第一个成员是sin_family,它是一个无符号短整数(unsigned short)。并不是所有的厂家都支持套接字地址结构的长度字段,而且POSIX规范也不要求有这个成员。
•即使有长度字段,我们也无须设置和检查它,除非涉及路由套接字。它是由处理来自不同协议族的套接字地址结构的例程(例如路由表处理代码)在内核中使用的。
•POSIX规范只需要这个结构中的3个字段:sin_family、sin_addr和sin_port。对于符合POSIX的实现来说,定义额外的结构字段是可以接受的,这对于网际套接字地址结构来说也是正常的。几乎所有的实现都增加了sin_zero字段,所以所有的套接字地址结构大小都至少是16字节。
•我们给出了字段s_addr、sin_family和sin_port的POSIX数据类型。in_addr_t数据类型必须是一个至少32位的无符号整数类型,in_port_t必须是一个至少16位的无符号整数类型,而sa_family_t可以是任何无符号整数类型。在支持长度字段的实现中,sa_family_t通常是一个8位的无符号整数,而在不支持长度字段的实现中,它则是一个16位的无符号整数。图2列出了POSIX定义的这些数据类型。
图2 POSIX规范要求的数据类型
•IPv4地址和TCP或UDP端口号在套接字地址结构中总是以网络字节序来存储。
•sin_addr字段是一个结构,而不仅仅是一个in_addr_t类型的无符号长整数,这是有历史原因的。早期的版本(4.2BSD)把in_addr结构定义为多种结构的联合(union),允许访问一个32位IPv4地址中的所有4个字节,或者访问它的2个16位值。这用在地址被划分成A、B和C三类的时期,便于获取地址中的适当字节。然而随着子网划分技术的来临和无类地址编排的出现,各种地址类正在消失,那个联合已不再需要了。如今大多数系统已经废除了该联合,转而把in_addr定义为仅有一个in_addr_t字段的结构。
•sin_zero字段未曾使用,不过在填写这种套接字地址结构时,我们总是把该字段置为0。按照惯例,我们总是在填写前把整个结构置为0,而不是单单把sin_zero字段置为0。尽管多数使用该结构的情况不要求这一字段为0,但是当捆绑一个非通配的IPv4地址时,该字段必须为0。
2、通用套接字地址结构
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递。然而以这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族的套接字地址结构。
在如何声明所传递指针的数据类型上存在一个问题。有了ANSI C后解决办法很简单:void*是通用的指针类型。然而套接字函数是在ANSI C之前定义的,在1982年采取的办法是在<sys/socket.h>头文件中定义一个通用的套接字地址结构,如图3所示:
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family; /* address family: AF_XXX value */
char sa_data[14]; /* protocol-specific address */
};
图3 通用套接字地址结构:sockaddr
于是套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为其参数之一,这正如bind函数的ANSI C函数原型所示:
int bind(int, struct sockaddr *, socklen_t);
3、值-结果参数
1)从进程到内核传递套接字地址结构的函数有3个:bind、connect和sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小,例如:
struct sockaddr_in serv;
/* fill in serv{} */
connect(sockfd, (struct sockaddr*)&serv, sizeof(serv));
2)从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername。这4个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的整数变量的指针。例如:
struct sockaddr_in cli;
socklen_t len;
len = sizeof(cli);
getpeername(sockfd, (struct sockaddr*)&cli, &len);
/* len may have changed */
把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值,它告诉内核该结构的大小,这样内核在写该结构时不至于越界;但函数返回时,结构大小又是一个结果,它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为值-结果参数。