问题描述
特别是 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); };
输出:
sin_addr in sockaddr_in = 0x7fffa26102b4 sin_addr in sockaddr_in6 = 0x7fffa26102b8
为什么这两个值不一样?
由于 this 指向相同的数据(要连接的地址),因此应该位于相同的地址.否则,您应该如何使用您不知道是 IPv4 或 IPv6 的 sockaddr_in 调用 inet_ntop ?
推荐答案
为什么这两个值不一样?
sockaddr_in 和 sockaddr_in6 是用于不同地址族(分别为 IPv4 和 IPv6)的不同结构.它们不需要以任何方式相互兼容,除了一个 - 第一个字段必须是一个 16 位整数来保存地址族.sockaddr_in 始终将该字段设置为 AF_INET,而 sockaddr_in6 始终将该字段设置为 AF_INET6.通过以这种方式标准化 family 字段,任何基于 sockaddr 的 API 都可以访问该字段并知道如何根据需要解释其余的结构数据.这也是为什么基于 sockaddr 的 API 通常也有一个 int 大小值作为输入/输出,因为 sockaddr_in 和 sockaddr_in6 是不同的字节大小,所以 API 需要能够验证您传递的任何缓冲区.
<块引用>由于 this 指向相同的数据(要连接的地址),因此应该位于相同的地址.
不,不应该.结构中地址字段的位置特定于结构所属的地址族的类型.没有要求 sockaddr_in 和 sockaddr_in6 应该将它们的地址存储在完全相同的偏移量处.
<块引用>否则,您应该如何使用您不知道是 IPv4 还是 IPv6 的 sockaddr_in 调用 inet_ntop ?
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), ...);
.
struct sockaddr_in6 sa; inet_ntop(AF_INET6, &(sa.sin6_addr), ...);
为了帮助您编写与家族无关的代码,您应该尽可能直接使用 sockaddr_storage 而不是 sockaddr_in 或 sockaddr_in6.sockaddr_storage 的大小足以容纳 sockaddr_in 和 sockaddr_in6 结构.由于两个结构都以相同的偏移量和大小定义了一个族字段,因此 sockaddr_storage 可以与任何对 sockaddr* 指针(connect()、accept()、bind()、getsockname()、getpeername() 进行操作的 API 一起使用等).
但是,inet_ntop() 不属于该类别,因此在使用 inet_ntop() 时必须手动拆开一个 sockaddr_storage,例如:
struct sockaddr_storage sa; switch (((sockaddr*)&sa)->sa_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; }