59

我正在查看 C 套接字中的 和 等函数,connect()bind()注意到它们采用指向结构的指针sockaddr。我一直在阅读并使您的应用程序独立于 AF,使用sockaddr_storage结构指针并将其转换为sockaddr指针很有用,因为它为更大的地址提供了所有额外空间。

我想知道的是函数connect()bind()请求sockaddr指针如何从指向比预期结构更大的结构的指针访问数据。当然,您将提供的结构的大小传递给它,但是函数用于从指向您已转换为的更大结构的指针中获取 IP 地址的实际语法是什么struct *sockaddr

这可能是因为我来自 OOP 语言,但这似乎有点像 hack 并且有点混乱。

4

2 回答 2

60

期望指针的函数struct sockaddr可能会在您向它们sockaddr发送指向struct sockaddr_storage. 这样,他们就可以像访问它一样访问它struct sockaddr

struct sockaddr_storage被设计成同时适合一个struct sockaddr_instruct sockaddr_in6

您不会创建自己的struct sockaddr,通常会根据您使用的 IP 版本创建一个struct sockaddr_in或一个。struct sockaddr_in6为了避免试图知道您将使用哪个 IP 版本,您可以使用struct sockaddr_storage其中一个可以保存的。这将依次struct sockaddr由 connect()、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];
};

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

于 2013-04-15T08:23:21.907 回答
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;

  }
}

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

所以实际上,你有一个struct sockaddr_inorstruct sockaddr_in6你(重新解释)转换为 astruct sockaddr并且bind()函数(和其他人)将该指针转换回 astruct sockaddr_instruct sockaddr_in6在他们检查sa_family成员并验证大小之后。

于 2015-12-12T04:25:25.407 回答