套接字系列函数如何处理不同类型的结构?

How socket family functions can work with different types of structures?

连接、绑定、接受等套接字函数如何处理不同大小的不同类型的结构?例如,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_... 类型。因此,您可以根据 ss_family 字段对 sockaddr_storage 进行类型转换。对于输入,类型转换为所需的 sockaddr_... 类型并根据需要填充其字段,包括 family。对于输出,请查看 ss_family 字段,然后根据需要将类型转换为适当的 sockaddr_... 类型。

例如,如果套接字的地址类型是 AF_INET (IPv4),connect() 要求 sockaddr 缓冲区为 sockaddr_in 格式,namelen 参数至少为 sizeof(sockaddr_in)。同样,accept() 使用 sockaddr_in 格式的数据填充 sockaddr 缓冲区,并且 addrlen 参数必须至少为 sizeof(sockaddr_in).

对于 AF_INET6 (IPv6),将 sockaddr_in 替换为 sockaddr_in6

其他功能也一样。

一般来说,sockaddr 缓冲区的大小必须足够大以容纳属于套接字地址类型的正确 sockaddr_... 结构。接受地址作为输入的函数(connect()bind()sendto())要求缓冲区以正确的 sockaddr_... 格式进行格式化。 return 地址作为输出(accept()recvfrom())的函数将使用适当的 sockaddr_... 类型格式化数据。

它部分是标准前 C 的遗留问题,当时情况更加宽松。例如,代码早于函数原型。

实际上,这些结构都有两个共同点:

  1. 第一个字段是 uint8_t 并给出结构的长度。
  2. 第二个字段是 sa_family_t,用于标识正在使用的结构类型。

C 标准说:

§6.5.2.3 Structure and union members

¶6 One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.

稍微改动一下,如果您将传递给套接字函数的类型视为指向各种不同类型的联合的指针,那么您可以看到代码可以访问前两个字段以确定是什么类型实际使用中

如果您从头开始重新设计套接字系统,我不相信这个设计是您现在想出的,但它仍然可以工作。