Quick-cocos2d-x 适配 IPV6

  1. 1. 一. IPV6 是啥 ?
  2. 2. 二. COCOS2D-X 适配 IPV6
    1. 2.1. 1. 下载 cocos2d-x-3rd-party-libs-bin
    2. 2.2. 2. 更新 curl
    3. 2.3. 3. 更新 websocket
  3. 3. 三. QUICK-COCOS2D-X 适配 IPV6
    1. 3.1. 1. 如何适配
    2. 3.2. 2. 如何判断一个服务器是 ipv6 还是 ipv4
  4. 4. 四. 还有什么要注意的
    1. 4.1. 1. 一定要使用使用域名
    2. 4.2. 2. 做好兼容性测试

一. IPV6 是啥 ?

这两天一个运营的同事跑过来问我:

他: 咱们的游戏适配那啥 VIP6 了么?
我: ….

苹果商店在儿童节之后就不允许未适配 IPV6 的应用上架了, IPV6 是啥 ? 需要做些什么呢 ?

看完这两篇文章就明白了:

iOS应用支持IPV6,就那点事儿
针对苹果最新审核要求为应用兼容IPv6

虽然这两篇文章是针对应用的, 但是从中我们也能了解大概的做法:

  1. 不建议使用底层的网络API
  2. 不要用IP地址

二. COCOS2D-X 适配 IPV6

cocos 依赖的三方库涉及到 IPV6 问题的库为 curl, websocket, cocos 自己提供的模块需要适配的有:

  1. HttpClient
  2. AssetsManagerEx
  3. SocketIO
  4. WebSocket
  5. Console
  6. ScriptingCore

cocos 官方也第一时间适配了 IPV6, 具体内容可以看这篇文章:儿童节后苹果爸爸只爱IPv6 Cocos2d-x第一时间支持, 里面描述的很简单:

如果你需要支持纯IPv6网络,只需要更新CURL和libwebsocket网络。

让我们尝试一下.

1. 下载 cocos2d-x-3rd-party-libs-bin

这一步看似很简单, 实则不然. 你不会想到从 github 上下载一个 100 多 MB 的文件是多麽困难, 为此我花费了近 2个小时的时间.

我下载的是 99 版的, 解压代用.

2. 更新 curl

拷贝解压出来的文件夹中的 curl 目录到 quick-cocos2d-x/external, 替换原来的 curl 目录.

打开 xcode, 编译, 发现有 100 多个错误:

image_1alkfke1vag91ccc1p15179c1jtcm.png-113.9kB

1
2
3
4
5
6
7
8
Undefined symbols for architecture i386:
"_ASN1_INTEGER_get", referenced from:
_ossl_connect_common in libcocos2d iOS.a(libcurl_la-openssl.o)
"_ASN1_STRING_data", referenced from:
_ossl_connect_common in libcocos2d iOS.a(libcurl_la-openssl.o)
"_ASN1_STRING_length", referenced from:
_ossl_connect_common in libcocos2d iOS.a(libcurl_la-openssl.o)
...

不要惊慌, 这是因为新版本 curl 多了一些静态库, 我们需要引入它们, 在 XCode 项目输找到 cocos2d_lib > external > curl > ios 目录上右键, 选择 Add Files to 'cocos2d_lib.xcodeproj':

image_1alkfv73t446cr3sc41r6r1hqj13.png-238.8kB

选中 libcrypto.alibssl.a, targets 选择 libcocos2d iOS, 确定, 重新编译, 搞定.

3. 更新 websocket

注: 我们项目并没有用到 websocket , 所以这里只是搞到编译通过, 运行时有木有问题就不到知道了!

和 curl 类似, 拷贝 cocos2d-x-3rd-party-libs 中的 websockets 目录到 quick-cocos2d-x/external, 替换原来的 websockets 目录.

因为最新版的 websockets api 变化挺大, 所以我们需要使用 cocos 最新的 WebSocket.h 和 WebSocket.cpp 替换 quick-cocos2d-x/cocos/network 中的 WebSocket .

编译, 会有一处错误发生在 WebSocket::WebSocket() 中, 因为 quick 中并没有 Director::EVENT_RESET 消息, 我们注释掉这段代码即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
// reserve data buffer to avoid allocate memory frequently
_receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE);
if (__websocketInstances == nullptr)
{
__websocketInstances = new (std::nothrow) std::vector<WebSocket*>();
}

__websocketInstances->push_back(this);

// std::shared_ptr<std::atomic<bool>> isDestroyed = _isDestroyed;
// _resetDirectorListener = Director::getInstance()->getEventDispatcher()->addCustomEventListener(Director::EVENT_RESET, [this, isDestroyed](EventCustom*){
// if (*isDestroyed)
// return;
// close();
// });
}

三. QUICK-COCOS2D-X 适配 IPV6

1. 如何适配

Quick-Cocos2d-x 中主要是 luasocket 的适配, 适配的方法就是选择性的调用 socket.tcp6()socket.tcp(), udp 也是如此.

Quick-Cocos2d-x 中调创建 socket 是在 SocketTCP:connect 函数中, 并没有预留 ipv6 参数, 我们需要添加一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SocketTCP:connect(__host, __port, __retryConnectWhenFailure, __ipv6)
if __host then self.host = __host end
if __port then self.port = __port end
if __retryConnectWhenFailure ~= nil then self.isRetryConnect = __retryConnectWhenFailure end
assert(self.host or self.port, "Host and port are necessary!")
--printInfo("%s.connect(%s, %d)", self.name, self.host, self.port)
if __ipv6 then
self.tcp = socket.tcp6()
else
self.tcp = socket.tcp()
end

self.tcp:settimeout(0)
...

然后, 如果要连接 ipv6 的服务器的话, __ipv6 值传 true 即可.

2. 如何判断一个服务器是 ipv6 还是 ipv4

lua 中 socket 的 dns 模块提供了一个函数 getaddrinfo, 可以返回一个服务器的 dns 解析结果数组, 其中一个很重要的字段就是 family, 有 inetinet6 两个值可选.

大家可以运行 dump(socket.dns.getaddrinfo("ipv6-test.com")) 看下结果:

1
2
3
4
5
6
7
8
9
10
- "<var>" = {
- 1 = {
- "addr" = "5.135.165.173"
- "family" = "inet"
- }
- 2 = {
- "addr" = "2001:41d0:8:e8ad::1"
- "family" = "inet6"
- }
- }

我们遍历一下这个结果, 如果其中有 "family" = "inet6" 的解析, 就可以使用 ipv6 进行连接了, 代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
local function isSupportIpv6(_domain)
local result = socket.dns.getaddrinfo(_domain)
local ipv6 = false
if result then
for k,v in pairs(result) do
if v.family == "inet6" then
ipv6 = true
break
end
end
end
return ipv6
end

我们将这个函数的结果作为 SocketTCP:connect 的最后一个参数传入即可.

四. 还有什么要注意的

1. 一定要使用使用域名

向苹果提审的服务器一定要使用域名, 所有的地方都是如此, 包括 http 请求, 热更新返回的 version.md 地址.

2. 做好兼容性测试

测试 IPV6 ONLY 的时候使用最新的 iOS 9.3 做测试, 苹果他们审核的时候也是只审核最新的系统是否正常.

但是我们实现的时候要兼容的不同的系统, 网络环境(IPV6 兼容IPV6 ONLY), 因为用户的网络可能是各种各样样, 这些组合都要测试到.