sockaddr、sockaddr_in和sockaddr_in6有什么区别?

57

我知道sockaddr_in是用于IPv4的,而sockaddr_in6用于IPv6。让我困惑的是sockaddr和sockaddr_in[6]之间的区别。

有些函数接受sockaddr,有些函数接受sockaddr_in或sockaddr_in6,所以:

  • 规则是什么?
  • 为什么需要两个不同的结构?

因为sizeof(sockaddr_in6) > sizeof(sockaddr) == sizeof(sockaddr_in),那么:

  • 这是否意味着我们应该始终使用sockaddr_in6来在堆栈中分配内存,并将其转换为sockaddr和sockaddr_in,以便支持ipv4和ipv6?

一个例子是:我们有一个套接字,我们想获取它的字符串IP地址(它可以是IPv4或IPv6)。

我们首先调用getsockname来获取addr,然后基于addr.sa_family调用inet_ntop。

这段代码有什么问题吗?

char ipStr[256];
sockaddr_in6 addr_inv6;
sockaddr* addr = (sockaddr*)&addr_inv6;
sockaddr_in* addr_in = (sockaddr_in*)&addr_inv6;

socklen_t len = sizeof(addr_inv6);
getsockname(_socket, addr, &len);

if (addr->sa_family == AF_INET6) {
    inet_ntop(addr_inv6.sin6_family, &addr_inv6.sin6_addr, ipStr, sizeof(ipStr)); 
    // <<<<<<<<IS THIS LINE VALID, getsockname expected a sockaddr, but we use 
    // it output parameter as sockaddr_in6.
} else {
    inet_ntop(addr_in->sin_family, &addr_in->sin_addr, ipStr, sizeof(ipStr));
}

有点相关 - https://dev59.com/lGEi5IYBdhLWcg3wpdfJ - sashoalm
2个回答

35

sockaddr_insockaddr_in6都是结构体,其中第一个成员是一个sockaddr结构。

根据C标准,结构体的地址和它的第一个成员的地址相同,因此您可以将指向sockaddr的指针强制转换为sockaddr_in(6)的指针。

接受sockaddr_in(6)作为参数的函数可能会修改sockaddr部分,并且接受sockaddr作为参数的函数只关心该部分。

这有点像继承。


1
很有趣,我在sockaddr_in和sockaddr的定义中没有看到继承关系。作为C语言的常见方式,我本来期望在sockaddr_in定义中会有一个sockaddr类型成员。但是我没有看到这个,至少在MAC上不是这样。那么这段代码片段的正确性如何?如果有效,那么getsockname预期会使用sockaddr,但在IPv6情况下会更改sockaddr之外的值。 - ZijingWu
2
sockaddr_in和sockaddr_in6都是结构体,其中第一个成员是sockaddr结构体。查看头文件时,我没有看到sockaddr_in具有类型为sockaddr的成员。 - MikeMB
4
我认为没有任何实现将struct sockaddr作为其他结构体(如struct sockaddr_in)的成员嵌入其中。它们所共有的只有一个成员sa_family,当你给定一个struct sockaddr时,你需要使用它来确定它实际上是哪种套接字地址结构。 - nos
在编译器优化变得极端激进之前,CIS规则被广泛解释为不需要将sockaddr嵌入到其他类型中。C99增加了一个要求,在访问点处必须可见包含这些结构的完整联合类型,但是像gcc这样的编译器假装标准要求直接通过结构类型进行访问,尽管标准没有这样的要求。 - supercat
经过对 sockaddr_in 结构体的检查,似乎没有名为 sockaddr 的成员。 - interesting

29
为了提供更多其他人可能会发现有用的信息,我决定回答我的问题,尽管我最初并没有打算这样做。
在查看了Linux源代码后,我发现以下内容: 存在多个协议,它们都实现了 getsockname。每个协议都有一个底层地址数据结构。例如,IPv4 有 sockaddr_in,IPv6 有 sockaddr_in6,AF_UNIX 套接字则有 sockaddr_un。 在 Linux 网络编程中,sockaddr 被用作通用数据结构的签名。该 API 将 socketaddr_in、socketaddr_in6 或 sockaddr_un 复制到基于另一个参数 length 的 sockaddr 中,通过 memcpy 进行操作。 所有这些数据结构都以相同类型字段 sa_family 开始。 由于这些原因,代码片段是有效的,因为 sockaddr_in 和 sockaddr_in6 都有 sa_family 字段,然后可以转换为正确的数据结构,在对该 sa_family 字段进行检查之后使用。 顺便说一句,我不确定为什么 sizeof(sockaddr_in6) > sizeof(sockaddr),这导致基于 sockaddr 的内存分配对于 ipv6 来说不够(容易出错),但我想这是由于历史原因。

9
请注意,你绝不能为一个 struct sockaddr 分配存储空间。你应该始终分配具体的结构体,比如 struct sockaddr_in、struct sockaddr_in6。或者你可以分配一个 struct sockaddr_storage,它保证足够大以容纳任何 struct sockaddr(不仅仅是 sockaddr_in 和 sockaddr_in6)。 - nos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接