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

时间:2013-04-15 08:20:25

标签: c sockets unix

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

我想知道的是connect()bind()之类的函数如何要求sockaddr指针从指针访问数据,指针指向比它更大的结构期待。当然,你传递它所提供的结构的大小,但是函数使用什么实际语法来获取指向更大结构的指针的IP地址,你已经转换为struct *sockaddr

这可能是因为我来自OOP语言,但它看起来像是一种乱七八糟的东西。

2 个答案:

答案 0 :(得分:44)

指向struct sockaddr的指针的函数可能会在向sockaddr发送指针时将指针发送到struct sockaddr_storage。通过这种方式,他们可以像struct sockaddr那样访问它。

struct sockaddr_storage旨在适合struct sockaddr_instruct sockaddr_in6

您不创建自己的struct sockaddr,通常会根据您使用的IP版本创建struct sockaddr_instruct sockaddr_in6。为了避免尝试知道您将使用的IP版本,您可以使用可以容纳的struct sockaddr_storage。这将由connect(),bind()等函数转换为struct sockaddr并以这种方式访问​​。

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

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个字节)。

答案 1 :(得分:10)

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

在C中,我们仅限于结构。但是,也可以为结构分配TAG。实际上,如果你看一下 theprole 在他的答案中发布的所有结构,你会发现它们都以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_instruct sockaddr_in6你(重新解释)强制转换为struct sockaddrbind()函数(和其他人)将该指针强制转换回来在检查struct sockaddr_in成员并验证尺寸后,struct sockaddr_in6sa_family