2016年6月1日,苹果出台新政策,要求所有提交Apple Store渠道的包必须要支持IPv6。这昂无疑让很多人心头一凉。
果然在这几天很多公司的产品都被打回来了。我们公司也不例外,提交了几个包,只有一款侥幸逃过一劫,其它全部被打回。
碰到这种问题,第一时间就去调查了下,首先肯定是去构建一个ipv6网络测试。面临着一大堆的问题,这个是客户端还是服务端的问题?如果是服务端的问题,怎么处理,服务端需要更改什么内容?如果是客户端的问题又怎么办呢?……
慌归慌,事情还是要去处理,首先去百度科普下苹果的这条新规的规则,然后再去熟悉下什么是ipv6(尴尬脸~)。这个就不赘述了,自行度娘。
怎么搭建ipv6的网络?
这个估计网上搜索一大堆,简单描述下。苹果机器是可以分享一个ipv6网络热点的。首先找一台苹果机器,连上网线(必须是以太网),然后再设置界面按option键,单机分享,就可以在互联网共享看到,创建NAT64网络的checkbox,我们勾选上,然后分享热点就可以了。一顿测试,发现很多游戏不能正常玩下去。
服务端需要修改吗?
服务端是否要修改,这个问题估计是大家比较关注的问题,因为如果服务端需要修改,那就麻烦了(可能其实并不麻烦~ 欠揍脸)。正好公司有oc写的客户端,拿了一个包在ipv6环境下测试,是正常的(因为实用了CFNETWork,苹果提供的兼容ipv6的网络库)。所以看得出来,服务端基本不需要修改。
需要在域名绑定的时候修改一个ipv6地址吗?
一开始我也纠结了下这个问题,是不是需要去绑定一个ipv6的地址。后来用oc写的客户端可以连接正常,说明基本没问题,不需要特别操作。那么基本得出结论,只要在客户端修改即可。
怎么改?
cocos2d-x 第一时间就更新了自身的curl和web socket。 http://forum.cocos.com/t/ipv6-only/36895
根据苹果官网提出的修改策略,我们的http、socket都需要支持。所以我们可以更新cocos团队提供的patch。简单解决http的问题。
现在最麻烦的事去修改socket,因为我们使用了BSDSocket的封装。网上并不难查到相关的资料,虽然基本都是如出一辙。
修改Socket
IPv4是“.”来分割的,但是IPv6是“:”来分割的。客户端如果是用苹果家族的语言开发的,我们基本不需要修改,或者是很少量的修改,因为他们本身提供的库就只支持。但是Cocos2d-x开发,我们一般会自行封装自己的Socket。一般来说是BSDSocket的使用。这时,我们就需要去做如下工作了。
一、将项目中的ip地址全部改成域名,因为IPv4的地址跟IPv6的地址不一样。另外解析域名的时候不再使用 gethostbyname函数,而是使用 getaddrinfo函数。添加一个网络判断的函数。
//判断是否IPv6网络
staticbool isIPV6Net(conststd::string domainStr = "www.baidu.com")
{
bool isIPV6Net = false;
struct addrinfo *result = nullptr,*curr;
struct sockaddr_in6 dest;
bzero(&dest, sizeof(dest));
dest.sin6_family = AF_INET6;
int ret = getaddrinfo(domainStr.c_str(),nullptr,nullptr,&result);
if (ret == 0)
{
for (curr = result; curr != nullptr; curr = curr->ai_next)
{
switch (curr->ai_family)
{
case AF_INET6:
{
isIPV6Net = true;
break;
}
case AF_INET:
break;
default:
break;
}
}
}
freeaddrinfo(result);
return isIPV6Net;
}
二、添加域名解析函数
static std::string domainToIP(const char* pDomain)
{
if (isIPV6Net())
{
struct addrinfo hint;
memset(&hint, 0x0, sizeof(hint));
hint.ai_family = AF_INET6;
hint.ai_flags = AI_V4MAPPED;
addrinfo* answer = nullptr;
getaddrinfo(pDomain, nullptr, &hint, &answer);
if (answer != nullptr)
{
char hostname[1025] = "";
getnameinfo(answer->ai_addr,answer->ai_addrlen,hostname,1025,nullptr,0,0);
char ipv6[128] = "";
memcpy(ipv6,hostname,128);
CCLOG("domainToIP addrStr:%s", ipv6);
return ipv6;
}
freeaddrinfo(answer);
}
else
{
struct hostent* h = gethostbyname(pDomain);
if( h != NULL )
{
unsigned char* p = (unsigned char *)(h->h_addr_list)[0];
if( p != NULL )
{
char ip[16] = {0};
sprintf(ip, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
return ip;
}
}
}
return "";
}
三、修改Socket初始化函数
m_uSocket = socket((isIPV6Net()?AF_INET6:AF_INET), SOCK_STREAM, IPPROTO_TCP);
四、Socket连接函数
Connect函数的第二个参数的获取
struct sockaddr* WWInetAddress::getSockaddr() const
{
return m_isNetWorkIpv6 ? (struct sockaddr*)&addr_v6 : (struct sockaddr*)&addr_v4;
}
第三个参数获取
int WWInetAddress::getLength()
{
return m_isNetWorkIpv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
}
连接Socket
int nRet = ::connect(m_uSocket, m_oInetAddress.getSockaddr(), m_oInetAddress.getLength());
五、其它需要注意事项
Socket发送接口及接收数据接口都不需要去处理,另外需要考虑的是Socket的重连,及网络切换的问题。不过这些都是流程代码,只要稍加注意即可。