我在C套接字中查看connect()
和bind()
等函数,并注意它们采用指向sockaddr
结构的指针。我一直在阅读并使你的应用程序独立于AF,使用sockaddr_storage
结构指针并将其转换为sockaddr
指针是有用的,因为它具有更大地址的所有额外空间。
我想知道的是connect()
和bind()
之类的函数如何要求sockaddr
指针从指针访问数据,指针指向比它更大的结构期待。当然,你传递它所提供的结构的大小,但是函数使用什么实际语法来获取指向更大结构的指针的IP地址,你已经转换为struct *sockaddr
?
这可能是因为我来自OOP语言,但它看起来像是一种乱七八糟的东西。
答案 0 :(得分:44)
指向struct sockaddr
的指针的函数可能会在向sockaddr
发送指针时将指针发送到struct sockaddr_storage
。通过这种方式,他们可以像struct sockaddr
那样访问它。
struct sockaddr_storage
旨在适合struct sockaddr_in
和struct sockaddr_in6
您不创建自己的struct sockaddr
,通常会根据您使用的IP版本创建struct sockaddr_in
或struct 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_in
或struct sockaddr_in6
你(重新解释)强制转换为struct sockaddr
而bind()
函数(和其他人)将该指针强制转换回来在检查struct sockaddr_in
成员并验证尺寸后,struct sockaddr_in6
或sa_family
。