套接字如何工作,连接,绑定,接受等,可以使用不同类型的不同大小的结构?例如,connect()将struct sockaddr作为其第二个参数,但是也可以传递struct sockaddr_in或struct sockaddr_in6,前提是第三个参数socklen_t namelen具有正确的值。但实际上,这些结构的格式不同:
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct sockaddr_in6 {
uint8_t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct sockaddr_storage {
uint8_t ss_len;
sa_family_t ss_family;
/* implementation-dependent fields */
};
这些结构似乎没有任何共同之处(实际上,只有sin_len和ss_family,但sin_len本身不可移植,并非所有平台都支持它),但我们对所有这些都使用相同的功能。我不认为这些函数仅仅依赖于第三个参数(namelen),因为根据对象的实际大小来确定它的类型是不可移植的。
结构的共同点是它们都以family
字段开始(所有平台上都没有len
字段),所有sockaddr_...
类型的偏移和大小都相同。该字段与套接字的实际地址类型(由socket()
或accept()
建立)相结合,足以使每个函数验证您传入的任何sockaddr
的大小和格式,因此它们可以报告错配上的错误。
sockaddr_storage
的尺寸足够大,可容纳任何其他sockaddr_...
结构类型。您可以将sockaddr_storage
传递给任何函数。您可以将其类型转换为任何其他sockaddr_...
类型。因此,您可以根据其sockaddr_storage
字段输入ss_family
。对于输入,输入到所需的sockaddr_...
类型并根据需要填充其字段,包括family
。对于输出,请查看ss_family
字段,然后根据需要键入 - 转换为适当的sockaddr_...
类型。
例如,如果套接字的地址类型是AF_INET
(IPv4),则connect()
要求sockaddr
缓冲区为sockaddr_in
格式,namelen
参数至少为sizeof(sockaddr_in)
。同样,accept()
使用sockaddr
格式的数据填充sockaddr_in
缓冲区,addrlen
参数必须至少为sizeof(sockaddr_in)
。
对于AF_INET6
(IPv6),将sockaddr_in
替换为sockaddr_in6
。
这同样适用于其他功能。
通常,sockaddr
缓冲区的大小必须足够大,以容纳属于套接字地址类型的正确sockaddr_...
结构。接受地址作为输入的函数(connect()
,bind()
,sendto()
)要求缓冲区以正确的sockaddr_...
格式进行格式化。返回地址作为输出的函数(accept()
,recvfrom()
)将使用适当的sockaddr_...
类型格式化数据。
当事情变得更加宽松时,这部分是来自预标准C的遗产。例如,代码早于函数原型。
实际上,这些结构都有两个共同点:
uint8_t
并给出结构的长度。sa_family_t
,用于标识正在使用的结构类型。C标准说:
§6.5.2.3 Structure and union members
¶6为了简化联合的使用,我们做了一个特殊的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查它们中任何一个的共同初始部分,可以看到完整类型的联合声明。如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列。
如果考虑传递给套接字函数的类型是指向各种不同类型的并集的指针,那么您可以看到代码可以访问前两个字段以确定实际使用的类型。
如果你从头开始重新设计插座系统,我不相信这个设计是你现在想出来的,但它仍然可以工作。