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

我正在查看函数,如在C套接字connect()bind() ,并注意到他们把指针指向一个sockaddr结构。 我一直在阅读和使应用程序AF-Independent,使用sockaddr_storage结构指针并将其转换为sockaddr指针非常有用,因为它具有用于较大地址的所有额外空间。

我想知道的是,像connect()bind()这样的要求sockaddr指针的函数是如何从一个指针访问数据,该指针指向的结构比所期望的大。 当然,你把它传递给你正在提供的结构的大小,但是函数使用的实际语法是什么,以便将IP地址从指向更大结构的指针转换为struct *sockaddr

这可能是因为我来自面向对象的语言,但它似乎是一种黑客和有点混乱。

期望指向struct sockaddr的指针的struct sockaddr可能会将指向struct sockaddr的指针发送给struct sockaddr_storage 。 就这样,他们像访问一个struct sockaddr一样访问它。

struct sockaddr_storage被devise为适合struct sockaddr_instruct sockaddr_in6

你不需要创build你自己的struct sockaddr ,你通常创build一个struct sockaddr_in或一个struct sockaddr_in6具体取决于你使用的是什么IP版本。 为了避免试图知道你将使用什么IP版本,你可以使用一个struct sockaddr_storage来保存。 这又会被types转换为由connect(),bind()等函数struct sockaddr并以这种方式访问​​。

您可以在下面看到所有这些结构(为了alignment,填充是特定于实现的):

 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; // eg AF_INET, AF_INET6 unsigned short sin_port; // eg 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是struct sockaddr的types,否则它将读取IPv6的全部16个字节)。

在具有至less一个虚函数的C ++类中,给定一个TAG。 该标签允许你dynamic_cast<>()到你的类派生的任何类,反之亦然。 TAG是允许dynamic_cast<>()工作的。 或多或less,这可以是一个数字或string…

在C中,我们仅限于结构。 但是,结构也可以分配一个TAG。 实际上,如果你看看在他的答案中发布的所有结构,你会注意到它们都是以2个字节(一个无符号的short)开始的,它代表了我们所说的地址族。 这确切地定义了结构是什么,因此它的大小,领域等

所以你可以做这样的事情:

 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 ++中调用的那样)指针。 数据在结构上是否正确取决于呼叫者。 这方面没有多lessselect。 这些函数需要在使用数据之前validation各种各样的东西,并在发现问题时返回-1和errno

所以实际上,你有一个struct sockaddr_instruct sockaddr_in6 ,你(重新解释)转换为一个struct sockaddrbind()函数(和其他人)检查sa_family成员之后将该指针转换回struct sockaddr_instruct sockaddr_in6validation了大小。