我正在试验 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 日:
这是使我对我的代码失败的原因感到困惑的相关文档:
“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 映射地址的数据包。(强调我的)”