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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16010622/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 06:02:14  来源:igfitidea点击:

Reasoning behind C sockets sockaddr and sockaddr_storage

csocketsunix

提问by Matt Vaughan

I'm looking at functions such as connect()and bind()in C sockets and notice that they take a pointer to a sockaddrstruct. I've been reading and to make your application AF-Independent, it is useful to use the sockaddr_storagestruct pointer and cast it to a sockaddrpointer because of all the extra space it has for larger addresses.

我看功能,如connect()bind()用C插座和通知,他们需要一个指向sockaddr结构。我一直在阅读并使您的应用程序与 AF 无关,使用sockaddr_storage结构指针并将其强制转换为指针很有用,sockaddr因为它具有用于较大地址的所有额外空间。

What I am wondering is how functions like connect()and bind()that ask for a sockaddrpointer go about accessing the data from a pointer that points at a larger structure than the one it is expecting. Sure, you pass it the size of the structure you are providing it, but what is the actual syntax that the functions use to get the IP Address off the pointers to larger structures that you have cast to struct *sockaddr?

我想知道是怎么样的功能connect()bind()对于一个问sockaddr指针去从一个指针,在一个比一个更大的结构点是希望访问数据。当然,您将所提供的结构的大小传递给它,但是函数用于从指向已转换为的更大结构的指针中获取 IP 地址的实际语法是struct *sockaddr什么?

It's probably because I come from OOP languages, but it seems like kind of a hack and a bit messy.

这可能是因为我来自 OOP 语言,但这似乎是一种黑客行为,而且有点混乱。

回答by theprole

Functions that expect a pointer to struct sockaddrprobably typecast the pointer you send them to sockaddrwhen you send them a pointer to struct sockaddr_storage. In that way, they access it as if it was a struct sockaddr.

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

struct sockaddr_storageis designed to fit in both a struct sockaddr_inand struct sockaddr_in6

struct sockaddr_storage被设计成既适合 astruct sockaddr_in又适合struct sockaddr_in6

You don't create your own struct sockaddr, you usually create a struct sockaddr_inor a struct sockaddr_in6depending on what IP version you're using. In order to avoid trying to know what IP version you will be using, you can use a struct sockaddr_storagewhich can hold either. This will in turn be typecasted to struct sockaddrby the connect(), bind(), etc functions and accessed that way.

您不会创建自己的struct sockaddr,您通常会根据您使用的 IP 版本创建 astruct sockaddr_in或 a struct sockaddr_in6。为了避免试图知道您将使用的 IP 版本,您可以使用struct sockaddr_storage可以容纳任何一个的。这将依次被struct sockaddrconnect()、bind() 等函数转换为类型并以这种方式访问​​。

You can see all of these structs below (the padding is implementation specific, for alignment purposes):

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

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];
};

So as you can see, if the function expects an IPv4 address, it will just read the first 4 bytes (because it assumes the struct is of type struct sockaddr. Otherwise it will read the full 16 bytes for IPv6).

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

回答by Alexis Wilke

In C++ classes with at least one virtual function are given a TAG. That tag allows you to dynamic_cast<>()to any of the classes your class derives from and vice versa. The TAG is what allows dynamic_cast<>()to work. More or less, this can be a number or a string...

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

In C we are limited to structures. However, structures can also be assigned a TAG. In fact, if you look at all the structures that theproleposted in his answer, you will notice that they all start with 2 bytes (an unsigned short) which represents what we call the family of the address. This defines exactly what the structure is and thus its size, fields, etc.

在 C 中,我们仅限于结构。但是,也可以为结构分配一个 TAG。事实上,如果您查看prole在他的回答中发布的所有结构,您会注意到它们都以 2 个字节(无符号短整型)开头,代表我们所说的地址族。这准确地定义了结构是什么,从而定义了它的大小、字段等。

Therefore you can do something like this:

因此,您可以执行以下操作:

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;

  }
}

As you can see, the function can check the lenparameter to make sure that the length is enough to fit the expected structure and therefore they can reinterpret_cast<>()(as it would be called in C++) your pointer. Whether the data is correct in the structure is up to the caller. There is not much choice on that end. These functions are expected to verify all sorts of things before it uses the data and return -1 and errnowhenever a problem is found.

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

So in effect, you have a struct sockaddr_inor struct sockaddr_in6that you (reinterpret) cast to a struct sockaddrand the bind()function (and others) cast that pointer back to a struct sockaddr_inor struct sockaddr_in6after they checked the sa_familymember and verified the size.

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