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_in
和struct 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_in
或struct sockaddr_in6
,你(重新解释)转换为一个struct sockaddr
, bind()
函数(和其他人)检查sa_family
成员之后将该指针转换回struct sockaddr_in
或struct sockaddr_in6
validation了大小。