一、IPv4套接字地址结构(struct sockaddr_in)
#include<netinet/in.h>
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*/
};
sin_len成员
- 长度字段sin_len是为增加对OSI协议的支持而随4.3BSD-Reno添加的。在此之前,第一个成员是sin_family(无符号整型(unsigned short))
- 并不是所有厂家都支持该字段,例如POSIX规范不要求有这个成员(Linux下就没有这个成员)
- 有了长度字段,简化了长度可变套接字地址结构的处理
- 即使有长度字段,我们也无须设置和检查它,除非设计路由套接字。它是由处理来自不同协议族的套接字地址结构的例程(例如路由表处理代码)在内核中使用的
sin_family成员
- 指定地址的协议族或协议域。使用AF_INET
sin_port成员
- 地址所绑定的端口,以网络字节序存储
sin_addr成员
- 该成员是一个结构体,指定地址的IP地址,也是网络字节序存储的
- 由于是一个结构体,所以在使用时需要注意(尤其是作为函数的参数传参时特别需要注意)。见下面案例
struct sockaddr_in serv; serv.sin_addr; //按照in_addr结构体引用其中的32位IPv4地址 serv.sin_addr.s_addr; //按照in_addr_t数据类型引用其中的32位IPv4地址
sin_zero成员
- 几乎所有的实现都使用该字段,所以所有的套接字地址结构大小都至少为16字节
- 由于不使用该成员,所以该成员总是被设置为0。尽管多数使用该结构的情况并不强制要求这一字段为0,但是当捆绑一个非通配的IPv4地址时,该字段必须为0
各个字段的数据类型
二、IPv6套接字地址结构(sockaddr_in6)
#include<netinet/in.h>
struct in6_addr{
unit8_t s6_addr[16]; /*s128-bit IPv6 address*/
/*network byte ordered*/
};
#define SIN6_LEN /*required for compile-time tests*/
struct sockaddr_in6{
uint8_t sin6_len; /*length of this struct (28)*/
sa_family_t sin6_family; /*AF_INET6*/
in_port_t sin6_port; /*transport layer port*/
/*network byte ordered*/
uint32_t sin6_flowinfo; /*flow information,undefined*/
struct in6_addr sin6_addr; /*IPv6 address*/
/*network byte ordered*/
uint32_t sin6_scope_id; /*set of interfaces for a scope*/
};
注意事项
- 如果系统支持sin6_len字段(套接字地址结构长度),那么SIN6_LEN常值必须定义
- IPV6地址族是AF_INET6
- 结构中字段的先后顺序做过编排,使得如果sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的。在一些64位处理机上,如果64位数据存储在某个64位边界位置,那么对它的访问将得到优化处理
- sin6_flowinfo字段分成两个字段:
- 低序20位是流标(flow label)
- 高序12位保留
- 对于具备范围的地址(scoped address),sin6_scope_id字段标识其范围(scope),最常见的是链路局部地址(link-local address)的接口索引(interface index)
三、通用套接字地址结构(struct sockaddr)
#include<sys/socket.h>
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family; /*address family:AF_XXX value*/
char sa_data[14]; /*protocol-specific address*/
};
通用套接字地址设计的目的:
- 处理套接字的函数(bind、connect等)的参数一般需要传递套接字的地址,然后对于不同协议簇的地址结构可能会不同,在传参时也就不同,为了统一传递一个地址结构,设计了这样的通用套接字地址结构
- 从应用程序开发人员的观点看,这些通用套接字地址结构的唯一用途就是对指向特定于协议的套接字地址结构的指针执行类型强制类型转换
- 因为套接字处理函数的参数为struct sockaddr类型,所以在传参时需要强制类型转换
传参的演示案例
- 例如我们调用bind函数绑定一个套接字地址
//bind函数的原型 int bind(int,struct sockaddr *,socklen_t);
//调用bind函数 struct sockaddr_int serv; bind(sockfd,(struct sockaddr*)&serv,sizeof(serv));
如果传参时未强制类型转换
- 如果在调用函数时没有强制类型转换,C编译器就会产生这样的警告信息“warning:passing arg 2 of 'bind' from incompatible pointer type.”(警告:把不兼容的指针类型传递给“bind”函数的第二个参数)
//调用bind函数 struct sockaddr_int serv; bind(sockfd,&serv,sizeof(serv)); //未强制类型转换
四、新的通用套接字地址结构(sockaddr_storage)
#include<netinet/in.h>
struct sockaddr_storage{
uint8_t ss_len; /*length of thos struct (implementation dependent)*/
sa_family_t ss_familt; /*address family;AF_XXX value*/
/*implementation-dependent elements to provide:
a)alignment sufficient to fulfill the alignmen requirements of
all socket address types that the system supports.
b)enough storage to hold any type of socket address that thw
system supports.
*/
};
注意事项
- 除了ss_len和ss_family之外,结构中的其它字段对用户来说是透明的
- sockaddr_storage结构必须类型强制转换成或复制到适合于ss_family字段所给出地址类型的套接字地址结构中,才能访问其他字段
与struct sockaddr结构体存在以下两点差别
- 如果系统支持的任何套接字地址结构有对齐需要,那么sockaddr_storage能够满足最苛刻的对齐要求
- sockaddr_storage足够大,能够容纳系统支持的任何套接字地址结构
五、套接字地址结构体的比较
- 我们对5种套接字地址结构进行了比较:IPv4、IPv6、Unix域、数据链路、存储
- 我们假设所有套接字地址结构都包含一个单字节长度字段,地址族字段也占用一个字节,其他所有字段都占用确切的最短长度
- IPv4、IPv6地址结构是固定长度的,而Unix域结构和数据链路结构是可变长度的。为了处理长度可变的结构,当我们把指向某个套接字地址结构的指针作为一个参数传递给某个套接字函数时,也把该结构的长度作为另一个参数传递给这个函数(我们在每个图片下面给出了套接字地址的字节数长度,这是4.4BSD的规范)
- sockaddr_un结构本身并非长度可变的(见域套接字的相关文章),但是其中的信息(即结构中的路径名)却是长度可变的。当传递指向这些结构的指针时,我们必须小心处理长度字段,包括 套接字地址结构本身的长度字段(如果其实现支持此字段),以及作为参数传给内核或从内核返回的长度
六、值-结构参数
- 概念:我们提到过,当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来 传递,也就是说传递的是指向该结构的一个指针。该结构的长度也作为一个参数来传递,不过 其传递方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程
从进程到内核
- 从进程到内核传递套接字地址结构的函数有3个:bind、connect和sendto。这些函数 的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小,例如:
struct sockaddr_in serv; /* fill in serv{} */ connect(sockfd, (SA *) &serv, sizeof(serv));
- 既然指针和指针所指内容的大小都传递给了内核,于是内核知道到底需从进程复制多少数据进来。下图展示了这个情形
- 设计用意:套接字地址结构大小的数据类型实际上是socklen_t,而不是int, 不过POSIX规范建议将socklen_t定义为uint32_t
从进程到内核
- 从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname 和getpeername。这4个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结 构大小的整数变量的指针。例如:
struct sockaddr_un cli; /* Unix domain */ socklen_t len; len = sizeof(cli); /* len is a value */ getpeername(unixfd, (SA *) &cli, &len); /* len may have changed */
- 设计用意:把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函 数被调用时,结构大小是一个值(value),它告诉内核该结构的大小,这样内核在写该结构时 不至于越界;当函数返回时,结构大小又是一个结果(result),它告诉进程内核在该结构中究 竟存储了多少信息。这种类型的参数称为值—结果(value-result)参数。下图展示了这个情形
备注:
在网络编程中,值—结果参数最常见的例子是所返回套接字地址结构的长度。不过本书中我 们还会碰到其他值—结果参数:
- select函数中间的3个参数
- getsockopt函数的长度参数
- 使用recvmsg函数时,msghdr结构中的msg_namelen和msg_controllen字段
- ifconf结构中的ifc_len字段
- ysctl函数两个长度参数中的第一个
七、INADDR_ANY与in6aadr_any
- 头文件:#include<netinet/in.h>
- 功能:如果不知道使用哪一个IP地址,那么使用这两个常量,会去告知内核自动选择合适的IP来使用
区别
- INADDR_ANY:用于IPV4(IPV4地址是一个32位的值)
struct sockaddr_int servaddr; servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
- in6addr_any:用于IPV6(IPV6地址存放在一个结构体中)
- 系统预先分配in6addr_any变量并将其初始化为常值AN6ADDR_ANY_INIT。头文件<netinet/in.h>中含有in6addr_any的extern声明
struct sockaddr_in6 serv; serv.sin6_addr=in6addr_any;
注意事项:
- 无论是网络字节序还是主机字节序,INADDR_ANY的值(为0)都一样,因此使用htonl并非必须。不过既然头文件<netinet/in.h>中定义的所有INADDR_常值都是按照主机字节序定义的,我们应该对任何这些常值都是用htonl