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

Posted

技术标签:

【中文标题】为啥我们在调用 bind() 时将 sockaddr_in 转换为 sockaddr?【英文标题】:Why do we cast sockaddr_in to sockaddr when calling bind()?为什么我们在调用 bind() 时将 sockaddr_in 转换为 sockaddr? 【发布时间】:2014-02-01 15:45:17 【问题描述】:

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

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

我无法理解为什么要使用 sockaddr_in 结构。为什么不直接准备并通过sockaddr

只是约定俗成吗?

【问题讨论】:

注意。 sockaddr_in6 也存在,任何编写新代码的人都应该包含它…… 您在代码中省略了一个非常重要的部分:name.sa_family = AF_INET for struct sockaddr_in!将struct sockaddr 视为所有其他 sockaddr 类型的联合。唯一的共同点是它们有一个第一个成员sa_family_t sa_family,它必须对应于实际的结构类型。 【参考方案1】:

不,这不仅仅是惯例。

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

【讨论】:

补充一句:sockaddr_in6 用于 IPv6 地址,sockaddr_un 用于 Unix 域套接字,... @MartinR 我也在考虑蓝牙(如果我没记错的话,Linux 通过套接字执行 RFCOMM)等等。 有很多我不知道的套接字类型......可能值得一提的是struct sockaddr_storage,它在某种意义上也是“通用”的,并且足够容纳任何类型套接字地址。【参考方案2】:

我不知道它是否与这个问题非常相关,但我想提供一些额外的信息,这可能会使 typecaste 更容易理解,因为许多没有花太多时间在 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 *) &amp;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-&gt;sa_len,编译器将从位置1000 访问到sizeof(__uint8_t),这与sockaddr_in 的字节大小相同。所以这应该访问相同的字节序列。

sa_family 的模式相同。

之后struct sockaddr 中有一个 14 字节字符数组,它存储来自in_port_t sin_port 的数据(typedef'd 16 位无符号整数 = 2 字节),struct in_addr sin_addr(只是一个 32 位 ipv4 地址 = 4字节)和char sin_zero[8](8 字节)。这 3 个加起来是 14 个字节。

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

user529758 的回答已经解释了这样做的原因。

【讨论】:

【参考方案3】:

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

【讨论】:

以上是关于为啥我们在调用 bind() 时将 sockaddr_in 转换为 sockaddr?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们需要在 ReactJS 中使用 bind() 来访问 this.props 或 this.state? [复制]

socket编程中为啥client端的可以不用bind函数绑定.而客户端必须呢?

为啥在链表中查找循环时将指针增加 2,为啥不增加 3、4、5?

为啥在 TCP 中使用 bind()?为啥它只用在服务器端而不用在客户端?

请求完成后触发 Redux 异步操作。为啥?

为啥不从入口点函数调用 FreeLibrary?