C套接字sockaddr和sockaddr_storage背后的推理

Mat*_*han 48 c unix sockets

我看功能,如connect()bind()用C插座和发现,他们需要一个指向sockaddr结构.我一直在阅读并使你的应用程序独立于AF,使用sockaddr_storage结构指针并将其转换为sockaddr指针是有用的,因为它具有更大的地址所需的额外空间.

我想知道的是,函数connect()bind()请求sockaddr指针的方法是如何从指针访问数据,指针指向比预期更大的结构.当然,你传递的是你提供它的结构的大小,但是函数用来从指向更大结构的指针获取IP地址的实际语法是struct *sockaddr什么?

这可能是因为我来自OOP语言,但它似乎有点乱,有点乱.

the*_*ole 46

期望指针的函数struct sockaddr可能在向它们sockaddr发送指针时将你发送给它们的指针强制转换为struct sockaddr_storage.通过这种方式,他们就像访问它一样访问它struct sockaddr.

struct sockaddr_storage设计适合a struct sockaddr_instruct sockaddr_in6

您不创建自己的struct sockaddr,您通常创建一个struct sockaddr_in或一个struct sockaddr_in6取决于您正在使用的IP版本.为了避免试图知道您将使用的IP版本,您可以使用struct sockaddr_storage哪个可以容纳.这将由struct sockaddrconnect(),bind()等函数进行类型转换,并以这种方式访问​​.

您可以在下面看到所有这些结构(填充是特定于实现的,用于对齐目的):

struct sockaddr {
   unsigned short    sa_family;    // address family, AF_xxx
   char              sa_data[14];  // 14 bytes of protocol address
};


struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};


struct sockaddr_in6 {
    u_int16_t       sin6_family;   // address family, AF_INET6
    u_int16_t       sin6_port;     // port number, Network Byte Order
    u_int32_t       sin6_flowinfo; // IPv6 flow information
    struct in6_addr sin6_addr;     // IPv6 address
    u_int32_t       sin6_scope_id; // Scope ID
};

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // all this is padding, implementation specific, ignore it:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,如果函数需要IPv4地址,它将只读取前4个字节(因为它假定结构是类型struct sockaddr.否则它将读取IPv6的完整16个字节).

  • 我正在读一本书,上面写着sockaddr不足以容纳sockaddr_in6. (5认同)
  • 好吧,sockaddr的大小是16,而sockaddr_storage的大小是128个字节.由于IPv6地址是16字节,因此很难看出sockaddr如何容纳IPv6地址. (3认同)

Ale*_*lke 10

在具有至少一个虚函数的C++类中,给出了TAG.该标签允许您访问dynamic_cast<>()您的类派生的任何类,反之亦然.TAG是允许dynamic_cast<>()工作的东西.或多或少,这可以是数字或字符串......

在C中,我们仅限于结构.但是,也可以为结构分配TAG.事实上,如果你看一下prole在他的答案中发布的所有结构,你会注意到它们都以2个字节(一个无符号的短)开头,它代表了我们称之为地址族的内容.这确切地定义了结构是什么,因此它的大小,字段等.

因此你可以这样做:

int bind(int fd, struct sockaddr *in, socklen_t len)
{
  switch(in->sa_family)
  {
  case AF_INET:
    if(len < sizeof(struct sockaddr_in))
    {
      errno = EINVAL; // wrong size
      return -1;
    }
    {
      struct sockaddr_in *p = (struct sockaddr_in *) in;
      ...
    }
    break;

  case AF_INET6:
    if(len < sizeof(struct sockaddr_in6))
    {
      errno = EINVAL; // wrong size
      return -1;
    }
    {
      struct sockaddr_in6 *p = (struct sockaddr_in6 *) in;
      ...
    }
    break;

  [...other cases...]

  default:
    errno = EINVAL; // family not supported
    return -1;

  }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,该函数可以检查len参数以确保长度足以满足预期的结构,因此它们可以reinterpret_cast<>()(就像在C++中调用的那样)指针.结构中的数据是否正确取决于调用者.在这方面没有太多选择.这些函数应该在使用数据之前验证所有类型的东西,并errno在发现问题时返回-1 .

因此,实际上,您有一个struct sockaddr_instruct sockaddr_in6那个(重新解释)强制转换为a struct sockaddrbind()函数(和其他人)将该指针强制转换为a struct sockaddr_in或者struct sockaddr_in6在检查sa_family成员并验证大小之后.