【问题标题】:Reasoning behind C sockets sockaddr and sockaddr_storageC 套接字 sockaddr 和 sockaddr_storage 背后的推理
【发布时间】:2013-04-07 07:09:40
【问题描述】:

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

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

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

【问题讨论】:

    标签: c sockets unix


    【解决方案1】:

    当您向它们发送指向struct sockaddr_storage 的指针时,需要指向struct sockaddr 的指针的函数可能会将您发送给它们的指针类型转换为sockaddr。这样,他们就可以像访问struct sockaddr 一样访问它。

    struct sockaddr_storage 旨在同时适应 struct sockaddr_instruct sockaddr_in6

    您不会创建自己的struct sockaddr,您通常会创建struct sockaddr_instruct sockaddr_in6,具体取决于您使用的IP 版本。为了避免试图知道您将使用什么 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 个字节)。

    【讨论】:

    • 所以假设我有一个指向struct sockaddr_storage 的指针,称为sas,并且结构中的所有字段都已正确填充了地址和地址族。我现在执行此操作:struct sockaddr *s = (struct sockaddr*)sas。我现在如何从s 获取地址?
    • 你不会直接使用struct sockaddr。你要做的就是把它重新输入sockaddr_storagesockaddr_in 然后阅读它。正如您从我的帖子中看到的那样,sockaddr 有足够的空间来容纳 IPv4 或 v6 地址。话虽如此,我不知道您为什么要将类型转换为struct sockaddr,而不是函数需要它的参数。 struct sockaddr 不适合“程序员使用”。
    • 我正在读一本书,上面说 sockaddr 不够大,无法容纳 sockaddr_in6。
    • 嗯,sockaddr 的大小是 16,而 sockaddr_storage 的大小是 128 字节。由于 IPv6 地址是 16 个字节,很难看出 sockaddr 将如何容纳 IPv6 地址。
    • 如果有人对 sa_len / sin_len 感到困惑:请参阅 stackoverflow.com/a/23627149/2013911(简而言之:这些属性是特定于实现的 => 不可移植)
    【解决方案2】:

    在 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&lt;&gt;()(因为它会在C++ 中调用)您的指针。结构中的数据是否正确取决于调用者。在这方面没有太多选择。这些函数有望在使用数据之前验证各种事情,并在发现问题时返回 -1 和 errno

    因此,实际上,您有一个 struct sockaddr_instruct sockaddr_in6 您(重新解释)转换为 struct sockaddrbind() 函数(和其他)将该指针转换回 struct sockaddr_in 或 @987654332 @ 在他们检查了sa_family 成员并验证了大小之后。

    【讨论】:

    • 虽然这不是一个 c++ 问题,但也许您可以澄清一下,并非所有 c++ 类都有 TAG,但只有具有至少一个虚函数的类。
    • @MikeMB OP 写道:“这可能是因为我来自 OOP 语言”,我认为这意味着他想了解 C 的工作原理与他之前所学的相比.
    • 对不起,我可能应该更清楚地表达自己:我对你引用 c++ 没有任何问题。我只是想确保 -虽然 这不是针对 c++ 程序员的问题 - 确实 引用 c++ 的部分是尽可能精确(以防 C++ 初学者像我一样绊倒它),
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-07
    • 2014-12-29
    • 2015-06-21
    • 2017-08-25
    • 2014-05-18
    • 2020-03-16
    • 1970-01-01
    相关资源
    最近更新 更多