7

我正在试验 IPv6 套接字,特别是 Windows Vista 及更高版本提供的“双栈”功能,显然默认情况下在 Unix 上提供。我发现当我将服务器绑定到特定 IP 地址或本地计算机的主机名解析时,我无法接受来自 IPv4 客户端的连接。但是,当我绑定到 INADDR_ANY 时,我可以。

请为我的服务器考虑以下代码。您可以看到我遵循 Microsoft 的建议创建 IPv6 套接字,然后将 IPV6_V6ONLY 标志设置为零:

addrinfo* result, *pCurrent, hints;

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // We intend to use the addrinfo in a call to connect().  (I know it is ignored if we specify a server to connect to...)

int nRet = getaddrinfo("powerhouse", "82", &hints, &result);

SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

int no = 0;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) != 0)
    return -1;

if (bind(sock, result->ai_addr, result->ai_addrlen) ==  SOCKET_ERROR)
    return -1;

if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
    return -1;

SOCKET sockClient = accept(sock, NULL, NULL);

这是我的客户的代码。您可以看到我创建了一个 IPv4 套接字并尝试连接到我的服务器:

addrinfo* result, *pCurrent, hints;

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

if (getaddrinfo("powerhouse", "82", &hints, &result) != 0)
    return -1;

SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
int nRet = connect(sock, result->ai_addr, result->ai_addrlen);

我的连接调用的结果始终是 10061:连接被拒绝。

如果我将服务器代码更改为绑定到 ::(或将 NULL 主机传递给 getaddrinfo()(同样的事情)),并更改我的客户端代码以在 getaddrinfo() 调用中指定 NULL 主机,则 V4 客户端可以连接美好的。

谁能解释一下为什么?如果我们想要双套接字行为,我还没有阅读任何必须指定 NULL 主机(因此使用 INADDR_ANY)的内容。这不是一个要求,因为我有一个多宿主主机并且我只想在一些可用的 IP 上接受 IPv4?

编辑 2013 年 5 月 15 日:

这是使我对我的代码失败的原因感到困惑的相关文档:

来自IPv6 Winsock 应用程序的双栈套接字

“Windows Vista 及更高版本提供了创建单个 IPv6 套接字的能力,该套接字可以同时处理 IPv6 和 IPv4 流量。例如,创建 IPv6 的 TCP 侦听套接字,进入双堆栈模式,并绑定到端口 5001。这个双-堆栈套接字可以接受来自连接到端口 5001 的 IPv6 TCP 客户端和来自连接到端口 5001 的 IPv4 TCP 客户端的连接。”

“默认情况下,在 Windows Vista 和更高版本上创建的 IPv6 套接字仅通过 IPv6 协议运行。为了使 IPv6 套接字成为双栈套接字,必须使用 IPV6_V6ONLY 套接字选项调用 setsockopt 函数以将此值设置为在套接字绑定到 IP 地址之前为零。当 IPV6_V6ONLY 套接字选项设置为零时,为 AF_INET6 地址族创建的套接字可用于发送和接收来自 IPv6 地址或 IPv4 映射地址的数据包。(强调我的)”

4

2 回答 2

8

IPv4 和 IPv6 是两个独立的协议。一种协议的数据包不能使用另一种协议来处理。这就是双栈概念存在的原因:您的系统同时运行 IPv4 和 IPv6 协议栈,同时具有 IPv4 和 IPv6 地址等。

操作系统有一个技巧,您可以拥有一个侦听所有 IPv4 和 IPv6 地址的 IPv6 套接字。您仍然需要在主机上拥有两个地址族,并且它仅在您绑定到通配符地址时才有效。一旦您将该套接字绑定到一个不再起作用的固定地址,它将仅适用于您绑定到的地址。

因此,如果您想监听所有可用地址,则将 IPV6_V6ONLY 设置为 0 并监听通配符地址即可。IPv4 客户端将显示为使用以::ffff:包含 IPv4 地址的最后 32 位开头的 IPv6 地址。

当您想绑定到特定地址时,您需要将套接字绑定到您要侦听的每个地址。然后您需要使用 ieselect(...)来监视这些套接字并响应那些因为有人连接到它们而变得活跃的套接字。

于 2013-05-10T12:14:13.647 回答
3

此链接http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch12lev1sec2.html提供了有关 IPv4 和 IPv6 连接的更多信息,

大多数双栈主机在处理监听套接字时应该使用以下规则:

  • 侦听 IPv4 套接字只能接受来自 IPv4 客户端的传入连接。
  • 如果服务器具有绑定通配符地址的侦听 IPv6 套接字并且未设置 IPV6_V6ONLY 套接字选项(第 7.8 节),则该套接字可以接受来自 IPv4 客户端或 IPv6 客户端的传入连接。对于来自 IPv4 客户端的连接,连接的服务器本地地址将是对应的 IPv4 映射的 IPv6 地址。
  • 如果服务器有一个侦听 IPv6 套接字,该套接字绑定了 IPv6 地址而不是 IPv4 映射的 IPv6 地址,或者绑定了通配符地址但设置了 IPv6_V6ONLY 套接字选项(第 7.8 节),则该套接字可以接受来自 IPv6 客户端的传入连接只要。
于 2015-03-08T10:05:32.433 回答