eastcowboy的专栏



花了两三天时间来学习luasocket,调试通过,运行正常,于是发文分享。

说明:文中的代码其实都是伪代码,只表明了思路,我并没有把调试通过的代码直接放上来。

准备工作

如何加载

luasocket官方文档~diego/software/luasocket/introduction.html,说的是使用require(‘socket’);

不过经过测试,其实在cocos2d-x中,应该使用require(‘socket.core’);才可以。

实际使用时我用的是local socket = require(‘socket.core’);

版本

socket._VERSION取得的值是“LuaSocket 3.0-rc1”,这比luasocket官方网站的版本(2.0.2)还要新。不过一圈实验下来,官方文档所描述的接口也都可用,没有体会到哪里有变化。

正题

1. 首先是创建socket,这使用socket.tcp()即可。

local clientSocket = socket.tcp();clientSocket:settimeout(0);注意我刚创建了socket就立刻设置timeout为0。这样一来,当我调用connect连接服务器的时候就不会导致整个游戏卡住了。

由于手机是作为游戏客户端,所以socket主要就是connect、send、receive这三个函数,外加select。至于bind、accept,这些是用在服务端的,我并没有花心思去体会。

2. 连接服务器。

if clientSockend:connect(address, port) == 1 then    cclog('socket connected');else    isConnecting = true;end

由于前面调用了settimeout(0),因此其实几乎不可能直接连接成功,这个connect函数就返回了。此时用一个变量isConnecting记录状态。

根据文档,可以用select函数查看,再确定是否真的连接成功。

3. select。

select函数会频繁调用,但我觉得我们的游戏对实时性要求不高,不必每帧都调用(流畅情况下每秒60次)。目前我每秒调用大约10次select函数。

根据select的结果,会处理发送数据、接收数据、以及关闭连接。

if isConnecting and socket.gettime() – connectTime > 10 then– 连接超时,关闭socket并清理所有变量。– 我还没有发现如何通过luasocket的接口来判断连接超时,所以只好用了这个笨办法return;endlocal arr = {clientSocket};local r, s, e = socket.select(arr, isConnecting and arr or nil, 0);if r and #r >= 1 and r[1] == clientSocket thenlocal recvData, recvError, recvParticialData = clientSocket:receive(99999999);if recvError == 'closed' then– socket已经断开return;end– 如果是大块数据,可能被拆分成多段,需要多次调用clientSocket:receive才能完成接收。– 因此可以把接收到数据先放到recvBuffer缓存起来。if recvData thenrecvBuffer = recvBuffer .. recvData;elseif recvParticialData thenrecvBuffer = recvBuffer .. recvParticialData;end– 处理缓存起来的数据。processRecvBuffer();endif s and #s >= 1 and s[1] == clientSocket thenif isConnecting then– 执行到此,说明连接已经成功了isConnecting = false;isConnected = true;end– 可以调用sendprocessSendBuffer();endif isConnected thenprocsesSendBuffer();end

由于socket本身的特点,

a. 本端调用一次send,对端可能需要多次receive才能完整接收。

b. 如果对一个很大的数据块进行send,可能无法一次性发送全部,而是只发送部分字节。剩余字节需要再次调用send才可发送。

对于a,

我把所有接收到的数据都储存到一个叫做recvBuffer的变量中,然后设置一个recvIndex作为索引。

recvIndex初始化为1,如果处理掉x个字节,那么就让recvIndex增加x。

while true do– 看消息头是否完整。目前我设计消息头有四个字段msgSize, msgType, msgSerial, msgCheck,都是USHORT类型。– 这里string.unpack其实是另一个库lpack提供的。cocos2d-x没有自带这个lpack库,不过它只有一个.c文件,,很容易就加入到工程中。local nextReceiveIndex, msgSize, msgType, msgSerial, msgCheck = string.unpack(receiveBuffer, '=HHHH', receiveIndex);if nextReceiveIndex – receiveIndex < 8 thenbreak;end– 根据消息头,看消息体是否完整if msgSize + receiveIndex > #receiveBuffer + 1 thenbreak;end– 消息体完整。处理这个消息。local reader = netmsgReadersMap[msgType];local handler = netmsgHandlersMap[msgType];if reader and handler thenlocal netmsg = reader(receiveBuffer, receiveIndex);if netmsg thenhandler(netmsg);endend– receiveIndex向后移动msgSize个字节。– 如果已经处理完所有消息,则清空整个缓冲。receiveIndex = receiveIndex + msgSize;if receiveIndex > #receiveBuffer thenreceiveBuffer = '';receiveIndex = 1;break;endend

对于b,

跟接收类似,receiveBuffer, receiveIndex对应有sendBuffer, sendIndex

local num, err, num2 = clientSocket:send(sendBuffer, sendIndex);if num then– 所有数据发送完毕sendBuffer = '';sendIndex = 1;else– 只有部分数据发送完毕sendIndex = num2 + 1;end

这样就基本完工了。

luasocket用的是string作为发送和接收的数据类型,由于lua的string其实可以保存任意数据(包括’\0’),所以这也不算什么问题。关键是在lua中要怎么把各种数值转化为二进制的string。目前我用了lpack这个库,比价简单,只有一个.c文件,加入到工程里面编译就行。在applicationDidFinishLaunching时,调用luaopen_pack就行了。

服务器我用的是C++,boost.asio和boost.circular_buffer搭配,还算简单。实际测试发送了60k字节的数据,运行正常。由于我的msgSize是一个USHORT,不支持大于64k字节,也就没有测试更大的数据包了。

另外编写了一些python脚本,用于自动生成代码,便于客户端和服务器解析网络收发的数据。这样整个网络模块就搭建完毕。

对于手机游戏来说,网络断开是常有的事情。对于网络时而断开,时而正常,目前我还没有在luasocket上做这方面的测试。

对于旅行,从来都记忆模糊。记不得都去了哪些地方,

eastcowboy的专栏

相关文章:

你感兴趣的文章:

标签云: