结构sockadr_in不应该适用于IPv4和IPv6吗?

chr*_*her 11 c sockets linux

具体来说,sin_addr似乎位于IPv4和IPv6套接字寻址的不同内存位置.这导致奇怪:

#include <stdio.h>                                                                               
#include <netinet/in.h>                                                                          

int main(int argc, char ** argv) {                                                               
  struct sockaddr_in sa;                                                                         
  printf("sin_addr in sockaddr_in  = %p\n", &sa.sin_addr);                                       
  printf("sin_addr in sockaddr_in6 = %p\n", &((struct sockaddr_in6*)&sa)->sin6_addr);            
};
Run Code Online (Sandbox Code Playgroud)

输出:

sin_addr in sockaddr_in  = 0x7fffa26102b4
sin_addr in sockaddr_in6 = 0x7fffa26102b8
Run Code Online (Sandbox Code Playgroud)

为什么这两个值不一样?

由于这指向相同的数据(要连接的地址),因此应该位于相同的地址.否则,你应该如何用sockaddr_in调用inet_ntop,你不知道是IPv4还是IPv6?

Rem*_*eau 26

为什么这两个值不一样?

sockaddr_in并且sockaddr_in6是用于不同地址系列的不同结构(分别是IPv4和IPv6).它们不需要以任何方式相互兼容,除了一个 - 第一个字段必须是16位整数来保存地址族. sockaddr_in始终将该字段设置为AF_INET,并sockaddr_in6始终将该字段设置为AF_INET6.通过以这种方式标准化族字段,任何sockaddr基于API的API都可以访问该字段,并且知道如何根据需要解释其余的结构数据.这也是为什么sockaddr基于API的原因通常也有int大小值作为输入/输出,因为sockaddr_in并且sockaddr_in6是不同的字节大小,因此API需要能够验证您传递的任何缓冲区的大小.

由于这指向相同的数据(要连接的地址),因此应该位于相同的地址.

不,它不应该.结构中地址字段的位置特定于结构所属的地址族的类型.没有要求sockaddr_in并且sockaddr_in6应该将它们的地址存储在完全相同的偏移处.

否则,你应该如何用sockaddr_in调用inet_ntop,你不知道是IPv4还是IPv6?

sockaddr_in只能与IPv4一起使用,而sockaddr_in6不能用于其他任何东西,只能与IPv6一起使用.如果你有一个,sockaddr_in那么你隐含地知道你有一个IPv4地址,如果你有一个,sockaddr_in6那么你隐含地知道你有一个IPv6地址.您必须指定该信息,inet_ntop()以便它知道如何解释传递给它的数据:

struct sockaddr_in sa;
inet_ntop(AF_INET, &(sa.sin_addr), ...);
Run Code Online (Sandbox Code Playgroud)

.

struct sockaddr_in6 sa;
inet_ntop(AF_INET6, &(sa.sin6_addr), ...);
Run Code Online (Sandbox Code Playgroud)

为了帮助您编写与家族无关的代码,您应该在可能的情况下sockaddr_storage代替sockaddr_in或sockaddr_in6直接使用. sockaddr_storage足够大,可容纳两者sockaddr_in和sockaddr_in6结构.由于这两种结构在相同的偏移量和大小定义一字段,sockaddr_storage可与上操作的任何API被使用sockaddr*指针(connect(),accept(),bind(),getsockname(),getpeername(),等等).

但是,inet_ntop()不属于该类别,因此您sockaddr_storage在使用时必须手动拉开inet_ntop(),例如:

struct sockaddr_storage sa;

switch (sa.ss_family)
{
case AF_INET:
    inet_ntop(AF_INET, &(((sockaddr_in*)&sa)->sin_addr), ...);
    break;
case AF_INET6:
    inet_ntop(AF_INET6, &(((sockaddr_in6*)&sa)->sin6_addr), ...);
    break;
}
Run Code Online (Sandbox Code Playgroud)

  • IPv4映射地址在技术上仍然是IPv6地址,因此它们仍然必须通过`sockaddr_in6`和`in6_addr`,而不是`sockaddr_in`和`in_addr`.除此之外,IPv6映射地址仅适用于支持双栈套接字的OS版本.并非所有操作系统都支持.例如,Windows XP支持IPv6但不支持双栈套接字.这是在Windows Vista中添加的. (3认同)
  • 实际上可以使用 sockaddr_in6 进行 IPv4 通信,方法是将 setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY) 设置为 false,并使用 IPv4 映射的 IPv6 地址来指定 IPv4 主机(例如 ::ffff:192.168.0.1 而不是 ::192.168 .0.1)。这很好,因为有了这个,应用程序可以与 IPv4 和 IPv6 客户端通信,而无需到处都使用 switch/case 语句。 (2认同)