2013-04-15 86 views
37

我正在查看函数,如connect()bind()在C套接字中,并注意他们采取指针sockaddr结构。我一直在阅读并使您的应用程序与AF无关,因此使用sockaddr_storage结构体指针并将其转换为sockaddr指针是有用的,因为它具有用于较大地址的所有额外空间。推理C插座sockaddr和sockaddr_storage

我想知道像connect()bind()是索要sockaddr指针去从一个指向一个比它期待一个更大的结构的指针访问数据的功能如何。当然,您将它传递给您提供的结构的大小,但是函数使用的实际语法是:将IP地址从指向更大结构的指针转到struct *sockaddr

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

回答

37

当您向sockaddr发送一个指向struct sockaddr_storage的指针时,希望指向struct sockaddr的函数可能会将您发送到sockaddr的指针类型化。通过这种方式,他们访问它就好像它是一个struct sockaddr

struct sockaddr_storage被设计以适应既是struct sockaddr_instruct sockaddr_in6

你没有创建自己的struct sockaddr,你通常会创建取决于你使用的IP版本struct sockaddr_instruct 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个字节)。

+0

因此,假设我有一个指向'struct sockaddr_storage'的指针,名为'sas',并且结构中的所有字段都已正确地填充了地址和地址系列。我现在执行这个:'struct sockaddr * s =(struct sockaddr *)sas'。我现在怎样才能把地址从's'中取出? –

+1

你不会直接使用'struct sockaddr'。你要做的就是将它转换回'sockaddr_storage'或'sockaddr_in',然后读取它。正如你可以从我的帖子看到'sockaddr'有足够的空间来适应IPv4或v6地址。话虽如此,我不知道为什么你会想为'struct sockaddr'强制转换为除了函数需要的参数之外的任何东西。 'struct sockaddr'不适用于'程序员使用'。 – theprole

+5

我正在读一本书,说sockaddr不够大,无法容纳sockaddr_in6。 –

7

在具有至少一个虚拟函数的C++类中给出一个TAG。该标签允许您将dynamic_cast<>()分配给您的班级派生的任何班级,反之亦然。标签是什么允许dynamic_cast<>()工作。或多或少,这可以是一个数字或一串...

在C中,我们只限于结构。但是,结构也可以分配一个TAG。事实上,如果你看看他的回答中发布的所有结构,你会注意到它们都以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_instruct sockaddr_in6你(重新诠释)强制转换为struct sockaddrbind()功能(及其他)施放该指针回struct sockaddr_instruct sockaddr_in6后,他们检查了sa_family成员并验证了大小。

+0

虽然这不是一个C++问题,也许你可以澄清,并非所有C++类都有一个TAG,但只有那些至少有一个虚函数。 – MikeMB

+1

@MikeMB OP写道:“这可能是因为我来自OOP语言”,我认为这意味着他想了解C的工作原理与他以前学习的相比。 –

+0

对不起,我可能应该更清楚地表达自己:我没有任何问题引用C++。我只是想确保 - ***虽然**这不是一个问题,正在解决C++程序员* - **做**参考C++的部分尽可能精确(以防C++初学者绊倒就像我一样), – MikeMB