为什么我们在调用bind()时将sockaddr_in转换为sockaddr?

时间:2014-01-13 18:51:57

标签: c linux sockets

bind()函数接受指向sockaddr的指针,但在我看过的所有示例中,都使用了sockaddr_in结构,并转换为sockaddr

struct sockaddr_in name;
...
if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
...

我无法理解为什么使用sockaddr_in结构。为什么不准备并传递sockaddr

这只是惯例吗?

3 个答案:

答案 0 :(得分:50)

不,这不仅仅是惯例。

sockaddr是任何类型的套接字操作的通用描述符,而sockaddr_in是特定于基于IP的通信的结构(IIRC,“in”代表“InterNet”)。据我所知,这是一种“多态”:bind()函数假装采用struct sockaddr *,但事实上,它会假设传递适当类型的结构;一世。即一个与你给它作为第一个参数的套接字类型相对应的。

答案 1 :(得分:4)

这是因为bind可以绑定除IP套接字之外的其他类型的套接字,例如Unix域套接字,它们的类型为sockaddr_un。 AF_INET套接字的地址将主机和端口作为其地址,而AF_UNIX套接字具有文件系统路径。

答案 2 :(得分:4)

我不知道它与这个问题是否非常相关,但是我想提供一些额外的信息,因为许多没有花很多时间与C在一起的人都可以使打字员更容易理解。看到这样的打字手感到困惑。

我使用macOS,所以我以基于系统头文件的示例为例。

struct sockaddr的定义如下:

struct sockaddr {
    __uint8_t       sa_len;         /* total length */
    sa_family_t     sa_family;      /* [XSI] address family */
    char            sa_data[14];    /* [XSI] addr value (actually larger) */
};

struct sockaddr_in的定义如下:

struct sockaddr_in {
    __uint8_t       sin_len;
    sa_family_t     sin_family;
    in_port_t       sin_port;
    struct  in_addr sin_addr;
    char            sin_zero[8];
};

从最基础的地方开始,一个指针仅包含一个地址。因此struct sockaddr *struct sockaddr_in *几乎相同。他们俩都只存储一个地址。唯一相关的区别是编译器如何处理其对象。

因此,当您说(struct sockaddr *) &name时,您只是在欺骗编译器并告诉它此地址指向struct sockaddr类型。


因此,假设指针指向位置1000。如果struct sockaddr *存储此地址,则它将根据结构定义考虑从1000sizeof(struct sockaddr)拥有成员的内存。如果struct sockaddr_in *存储相同的地址,它将考虑从1000sizeof(struct sockaddr_in)的内存。


当您键入该指针时,它将考虑到sizeof(struct sockaddr)为止的相同字节序列。

struct sockaddr *a = &name; // consider &name = 1000

现在,如果我访问a->sa_len,则编译器将从位置1000sizeof(__uint8_t)进行访问,该地址的字节大小与sockaddr_in相同。因此,这应该访问相同的字节序列。

相同的模式适用于sa_family

此后,struct sockaddr中有一个14字节的字符数组,用于存储in_port_t sin_porttypedef个16位无符号整数= 2个字节)中的数据,struct in_addr sin_addr(只需一个32位ipv4地址= 4字节)和char sin_zero[8](8字节)。这三个加起来就是14个字节。

现在这三个存储在这14个字节的字符数组中,我们可以通过访问适当的索引并再次进行类型转换来访问这三个中的任何一个。

user529758的答案已经说明了这样做的原因。