这是Connecting IPv4 client to IPv6 server: connection refused的延续 . 我正在尝试使用双堆栈套接字并尝试了解哪些setsockopt与IPV6_V6ONLY有用 . 关于相关问题,我被告知"Setting IPV6_V6ONLY to 0 can be useful if you also bind the server to an IPv6-mapped IPv4 address" . 我在下面做了这个,并期望我的服务器能够接受来自IPv6和IPv4客户端的连接 . 但令人震惊的是,当我使用V4和V6插槽运行我的客户端时,两者都无法连接!
有人可以告诉我我做错了什么,还是我误解了IPv6双栈功能?
服务器:
void ConvertToV4MappedAddressIfNeeded(PSOCKADDR pAddr)
{
// if v4 address, convert to v4 mapped v6 address
if (AF_INET == pAddr->sa_family)
{
IN_ADDR In4addr;
SCOPE_ID scope = INETADDR_SCOPE_ID(pAddr);
USHORT port = INETADDR_PORT(pAddr);
In4addr = *(IN_ADDR*)INETADDR_ADDRESS(pAddr);
ZeroMemory(pAddr, sizeof(SOCKADDR_STORAGE));
IN6ADDR_SETV4MAPPED(
(PSOCKADDR_IN6)pAddr,
&In4addr,
scope,
port
);
}
}
addrinfo* result, hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
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;
ConvertToV4MappedAddressIfNeeded(result->ai_addr);
if (bind(sock, result->ai_addr, 28/*result->ai_addrlen*/) == SOCKET_ERROR)
return -1;
if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
return -1;
SOCKET sockClient = accept(sock, NULL, NULL);
printf("Got one!\n");
客户:
addrinfo* result, *pCurrent, hints;
char szIPAddress[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof hints); // Must do this!
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
const char* pszPort = "82";
if (getaddrinfo("powerhouse", "82", &hints, &result) != 0)
return -1;
SOCKET sock = socket(AF_INET, result->ai_socktype, result->ai_protocol);
int nRet = connect(sock, result->ai_addr, result->ai_addrlen);
1 回答
我的C技能有点生疏,所以这是一个用Python编写的反例 . 我的本地IPv4地址是37.77.56.75,这就是我要绑定的地址 . 我保持尽可能简单,专注于概念 .
这是服务器端:
这里我们绑定代码中的IPv6地址,但该地址实际上是IPv6映射的IPv4地址,因此实际上我们绑定到IPv4地址 . 在查看ie netstat时可以看到:
然后我们可以使用IPv4客户端连接到此服务器:
服务器将使用IPv6地址表示向我们显示连接的人:
在此示例中,我从IPv4主机
37.77.56.76
连接,并选择端口50887
进行连接 .在此示例中,我们仅侦听IPv4地址(使用IPv6套接字,但它仍然是IPv4地址),因此仅IPv6客户端将无法连接 . 具有IPv4和IPv6的客户端当然可以使用具有IPv6映射的IPv4地址的IPv6套接字,但是它实际上不会使用IPv6,只是IPv4连接的IPv6表示 .
双栈服务器必须:
侦听通配符地址,这将使操作系统接受任何地址(IPv4和IPv6)上的连接
侦听IPv6地址和IPv4地址(通过创建IPv4套接字,或通过创建IPv6套接字并侦听IPv6映射的IPv4地址,如上所示)
使用通配符地址是最简单的 . 只需使用上面的服务器示例并替换主机名:
我的Mac OS X框显示为:
注意
tcp46
表示它侦听两个地址族 . 不幸的是,在Linux上它只显示tcp6
,即使在收听两个家庭时也是如此 .现在是最复杂的例子:监听多个套接字 .
运行此示例时,两个套接字都可见:
现在,仅IPv6的客户端可以连接到
2a00:8640:1::224:36ff:feef:1d89
,而仅IPv4的客户端可以连接到37.77.56.75
. 双栈客户端可以选择要使用的协议 .