为啥我们在调用 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 *) &name
时,你只是在欺骗编译器并告诉它这个地址指向struct sockaddr
类型。
假设指针指向位置1000
。如果struct sockaddr *
存储了这个地址,它会根据结构定义认为从1000
到sizeof(struct sockaddr)
的内存拥有成员。如果struct sockaddr_in *
存储相同的地址,它将考虑从1000
到sizeof(struct sockaddr_in)
的内存。
当您对该指针进行类型转换时,它将考虑到 sizeof(struct sockaddr)
之前的相同字节序列。
struct sockaddr *a = &name; // consider &name = 1000
现在如果我访问a->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?