首先別錯誤理解以為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 }