目前博客或者论坛中,几乎很少有cocos2dx-lua socket连接发送protobuf的博客,我也走了很多弯路才调通了protobuf。现在给大家详细的写下protobuf的流程,大牛们不要喷,小弟不才,帮忙看看还有什么漏洞。
一.lua-protobuf,目前网上cocos2dx-lua去集成protobuf的例子有很多,本篇主要去介绍怎么去使用protobuf,所以在这里我给大家发一个链接(转载别人的,这个人写的很详细,有问题也可以私信我)《cocos2dx 3.x 集成protobuf》。
二.lua-socket,我这里就以cocos2dx-lua 3.3为例子。
(1)首先创建一个socket
require("socket")
require("protobuf")--集成的protobuf
local n,receive_status = socket:connect("192.168.1.125", 9999)
print(n,receive_status)--是否连接正常
socket:settimeout(0)
到这里已经完成了socket的创建。接下来就是两个很重要的方法,send和receive,很多人会说lua发送数据给后台直接发json啊,但是这里在效率上是比不过protobuf的,这个知识点大家可以看看《protobuf与json》。
(2)send方法遇到的问题以及解决方法
local mmoPbFilePath = cc.FileUtils:getInstance():fullPathForFilename("res/MMO.pb")
local mmoBuffer = read_protobuf_file_c(mmoPbFilePath)
protobuf.register(mmoBuffer)
local mmobufferStr = protobuf.encode("MMOProto.Hello",{
id = 999,
name="小明",
isMale = false,
})
在没了解protobuf之前我直接用
socket:send(mmobufferStr)
这导致服务器一直收不到数据,这就欠缺了分包粘包的概念的概念。可以参考《socket分包和粘包》。这里简单介绍下socket分包粘包的三种方法:
1.消息头,消息体:消息头笼统的来说就是消息体的长度,根据消息头的长度去获取消息体,所以说消息头的长度如果不正确必会影响到消息体的完整程度,甚至报错;
2.定长:固定一个长度发送,这样的弊端就是消息体不能超过定长;
3.标记位:在一个消息的末尾加上自己定位的结束位,例如"hello world()()()()",那么()()()()这个就是结束的标记。(这个最好不要和会出现的字符串冲突。我这里只是简单介绍下,并没有去很好的设计)。
我们这里采取的方法是消息头,消息体的方法,也是效率最高的方法。那么这里就要需要获取mmobufferStr的消息头,也就是长度。到这里又一个坑要出现了。长度不单单是string.len(mmobufferStr)去得到长度,还需要把这个长度转成二进制的形式发送。这里我也搜过很多帖子,也是有位大佬有一套完善的转换方法。转载自http://blog.csdn.net/u013654125/article/details/77184616
-- 工具类
Utils = class("Utils")
-- 下面的二进制=ascii
-- 二进制转int
function Utils:bufToInt32(num1, num2, num3, num4)
local num = 0;
num = num + self:leftShift(num1, 24);
num = num + self:leftShift(num2, 16);
num = num + self:leftShift(num3, 8);
num = num + num4;
return num;
end
-- int转二进制
function Utils:int32ToBufStr(num)
local str = "";
str = str .. self:numToAscii(self:rightShift(num, 24));
str = str .. self:numToAscii(self:rightShift(num, 16));
str = str .. self:numToAscii(self:rightShift(num, 8));
str = str .. self:numToAscii(num);
return str;
end
-- 二进制转shot
function Utils:bufToInt16(num1, num2)
local num = 0;
num = num + self:leftShift(num1, 8);
num = num + num2;
return num;
end
-- shot转二进制
function Utils:int16ToBufStr(num)
local str = "";
str = str .. self:numToAscii(self:rightShift(num, 7));
str = str .. self:numToAscii(num);
return str;
end
function Utils:int8ToBufStr(num)
local str = "";
str = str .. self:numToAscii(num);
return str;
end
-- 二进制转Ascii
function Utils:bufToInt16(num1, num2)
local num = 0;
num = num + num2;
return num;
end
function Utils:leftShift(num, shift)
return math.floor(num * (2 ^ shift));
end
function Utils:rightShift(num, shift)
return math.floor(num/(2^shift));
end
function Utils:numToAscii(num)
num = num % 256;
return string.char(num);
end
接下来我们来组消息头消息体的数据:
local slen = string.len(stringbuffer)
local stringNumByte = Utils:int32ToBufStr(slen)
socket:send(stringNumByte)--发送消息头
socket:send(stringbuffer)--发送消息体
这样send数据就大功告成了。
(3)receive:这里只需要一个调度器去时时接收服务器数据
cc.Director:getInstance():getScheduler():scheduleScriptFunc(function ( )
local chunk ,status, partial = socket:receive(4)--首先接受4字节的消息头,根据数据头算出消息提的长度
if chunk~=nil and chunk~="" then
local num = {}
for i=1,string.len(chunk) do
num[i] = string.byte(chunk,i,i)
end
local longBuff = Utils:bufToInt32(num[1],num[2],num[3],num[4])--得出消息体的长度
local chunk ,status, partial = socket:receive(longBuff)--去接收消息体
if chunk~=nil and chunk~="" then
local sendChunk = chunk
-- print(chunk)
local rootResult = protobuf.decode("RootProto.Msg",chunk)
if rootResult==false then
return
end
local data = rootResult.data
local result = protobuf.decode(rootResult.type,data)
if result==false then
return
end
print(result.name)
end
end
end, 1/30,false)
注:这里解释下为什么接收数据头是4字节的,首先4字节对于我们传输的数据是完全够用的,其次数据头编码方式采用varint32编码,他这个编码是灵活多变的,最大支持4字节,我们这里直接干掉了varint32判断长度去得到字节数,直接规定4字节。(大家可以随意改,这个就是你和服务器商量来的事情了,怎么做怎么好~)。
如果大家有什么问题还可以留言讨论下,也提前感谢下各路大佬对我的错误的指出,我会不断完善!如果有错误大佬可以在评论指出下哦!