首先别错误理解以为app 必须支持 ipv6 的服务端, 只需要支持 ipv6的客户端需要访问 ipv4 的服务端。
目前需要经过 NAT64 转换就能达到目的,客户端只需要支持该转换就能实现, 在OS X的系统上是的wifi 共享有支持 NAT64转换,提供了这个功能就方便了我们可以使用手机链接 OS X上的热点模拟这个测试环境。
我们代码上所需要完成的事情:
1.客户端需要链接的ip地址(ipv4) 通过转换。
1 getaddrinfo //方法可以转换为 ipv6 的地址。 2 3 struct addrinfo hints, *res, *res0; 4 5 memset(&hints, 0, sizeof(hints)); 6 7 hints.ai_family = PF_UNSPEC; 8 9 hints.ai_socktype = SOCK_STREAM; 10 11 #if (PLATFORM == PLATFORM_WIN32) 12 13 hints.ai_flags = AI_PASSIVE; 14 15 #else 16 17 hints.ai_flags = AI_DEFAULT; 18 #endif
19 int error = getaddrinfo(pServerIP, "http", &hints, &res0);
2.经过转换后我们可以判断 ai_family 兼容 非NAT64 转换的网络环境与 NAT64转换的网络环境 下进行 socket的链接:
1 struct sockaddr_in addr_in; 2 3 struct sockaddr_in6 addr_in6; 4 5 int connect_ret; 6 7 for (res = res0; res; res = res->ai_next) { 8 9 connect_ret = -1; 10 11 tempSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 12 13 if (tempSocket < 0) { 14 15 continue; 16 17 } 18 19 if(AF_INET == res->ai_family){ 20 21 addr_in = *((struct sockaddr_in *)res->ai_addr); 22 23 addr_in.sin_port = htons(ServerPort); 24 25 connect_ret = connect(tempSocket, (struct sockaddr *)&addr_in, res- >ai_addrlen); 26 27 }else if(AF_INET6 == res->ai_family){ 28 29 addr_in6 = *((struct sockaddr_in6 *)res->ai_addr); 30 31 addr_in6.sin6_port = htons(ServerPort); 32 33 connect_ret = connect(tempSocket, (struct sockaddr *)&addr_in6, res->ai_addrlen); 34 35 } 36 37 if (connect_ret < 0) { 38 39 cause = "connect"; 40 41 #if (PLATFORM == PLATFORM_WIN32) 42 43 closesocket(tempSocket); 44 45 #else 46 47 close(tempSocket); 48 49 #endif 50 51 tempSocket = -1; 52 53 continue; 54 55 } 56 57 break; 58 59 }
OK,这样就支持 ipv6 对 ipv4 的网络 而且兼容 普通 ipv4 对 ipv4 的网络。
上面是没有考虑链接时候客户端会进入卡死状态直到链接成功, 因为加上去代码会复杂点,为了好看就没有加上, 真实项目中需要改为非阻塞模式, 通过select 来判断是否connect成功。
下面是加上非阻塞模式:
1 int tempSocket = -1; 2 struct addrinfo hints, *res, *res0; 3 int error; 4 const char *cause = NULL; 5 6 memset(&hints, 0, sizeof(hints)); 7 hints.ai_family = PF_UNSPEC; 8 hints.ai_socktype = SOCK_STREAM; 9 10 error = getaddrinfo(pServerIP, NULL, &hints, &res0); 11 if (error) { 12 CCLOG("getaddrinfo error:%d", error); 13 return false; 14 } 15 16 17 struct sockaddr_in addr_in; 18 struct sockaddr_in6 addr_in6; 19 for (res = res0; res; res = res->ai_next) { 20 tempSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 21 if (tempSocket < 0) { 22 cause = "socket"; 23 continue; 24 } 25 26 #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) 27 // 设定 非阻塞 28 unsigned long tmepOption = 1; //0:阻塞 29 ioctlsocket(tempSocket, FIONBIO, &tmepOption); 30 #else 31 // 设定 非阻塞 32 int flag = fcntl(tempSocket, F_GETFL, 0); 33 fcntl(tempSocket, F_SETFL, flag | O_NONBLOCK); 34 #endif 35 36 if(AF_INET == res->ai_family){ 37 addr_in = *((struct sockaddr_in *)res->ai_addr); 38 addr_in.sin_port = htons(ServerPort); 39 connect(tempSocket, (struct sockaddr *)&addr_in, res->ai_addrlen); 40 }else if(AF_INET6 == res->ai_family){ 41 addr_in6 = *((struct sockaddr_in6 *)res->ai_addr); 42 addr_in6.sin6_port = htons(ServerPort); 43 connect(tempSocket, (struct sockaddr *)&addr_in6, res->ai_addrlen); 44 } 45 46 fd_set fset; 47 FD_ZERO(&fset); 48 FD_SET(tempSocket, &fset); 49 50 timeval timeout; 51 timeout.tv_sec = 1; // 1.5秒超时, 上层逻辑会尝试3次 >> 1.5 * 3 52 timeout.tv_usec = 500; 53 #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) 54 socklen_t len = sizeof(error); 55 #else 56 int len = sizeof(error); 57 #endif 58 if( select(tempSocket+1, NULL, &fset, NULL, &timeout) > 0) 59 { 60 if (FD_ISSET(tempSocket, &fset)) 61 { 62 getsockopt(tempSocket, SOL_SOCKET, SO_ERROR, (char *)&error, &len); 63 if(error == 0){ 64 break; 65 } 66 } 67 } 68 69 #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) 70 closesocket(tempSocket); 71 #else 72 close(tempSocket); 73 #endif 74 tempSocket = -1; 75 76 } 77 78 if (tempSocket < 0) { 79 CCLOG("connect server error:%d", tempSocket); 80 goto RETURN_FALSE; 81 } 82 83 84 if(false){ 85 RETURN_FALSE: 86 freeaddrinfo(res0); 87 return false; 88 }