ToLua SimpleFramework NGUI/UGUI基础知识[4]
原文地址:http://doc.ulua.org/default.asp?cateID=4
视频地址:http://pan.baidu.com/s/1gd8fG4N
周六早晨,咱接着更新,这次咱们说下SimpleFramework使用的4种网络协议层:bytebuffer、protobuf_lua_gen、pbc、sproto。
(1)bytebuffer:这个只是框架里面的一个c#类,起源很早了,大概是在我2012年创业的时候就有它了,它就是一个二进制socket协议字节流操作的类,那干吗用它来命名?因为大家都熟悉它,我也用惯了。ULUA支持它应该是协议层最早的,所以上面的顺序都是按照时间来排的。我觉得这个方式最没有什么什么可说的,因为它就是用Wrap的方式将这个类注册进Lua中,然后通过将所有类型的值压入一个队列,最终ToArray变成一个byte[]变量,然后通过c#的socket发出去。唯一值得说的是,目前新版框架的其他协议都依靠它作为基础,后面细说。这里补个PromptPanel.lua文件中的例子看下:
function PromptCtrl.TestSendBinary()
local buffer = ByteBuffer.New();
buffer:WriteShort(Login);
buffer:WriteByte(ProtocalType.BINARY);
buffer:WriteString("ffff我的ffffQ靈uuu");
buffer:WriteInt(200);
NetManager:SendMessage(buffer);
end
这里面最后发送消息是通过C#网络管理器,打开Scripts/Manager/NetworkManager.cs
public void SendMessage(ByteBuffer buffer) {
SocketClient.SendMessage(buffer);
}
那SocketClient也就是将Socket封装进Module代理层的SocketClient,接着跟进去:
SendMessage->SessionSend->WriteMessage(这里数据已经从ByteBuffer转换成了byte[])->BeginWrite.终于跟到头了,消息也就发送出去了。
我的函数命名规范应该很清晰了,都是一眼都能看出来的,没有什么乱七八糟的缩写。就是New开辟了一个数据块,然后往里面塞东西,那相应的服务器端,也得这样顺序读取才是。
这是发送,接收咧?对了,想想昨晚上一篇帖子,写的是啥,看下在SocketCommand.cs里面的代码:
KeyValuePair<int, ByteBuffer> message = (KeyValuePair<int, ByteBuffer>)body;
switch (message.Key) {
default: Util.CallMethod("Network", "OnSocket", message.Key, message.Value); break;
}
它把所有的从NetworkManager接收到的数据一股脑的全Post给了Lua的Network模块,(其实如果你C#有需要,这里可以分开,这也是为啥从NetworkManager还要传递给SocketCommand的原因)让Lua再次分发,那我们看下Lua/Logic/Network.lua文件中模块怎么写的:
这个Lua文件,在一开始Network.Start()就注册了大量的事件监听,当然在最后Network.Unload(),也要有移除监听,好习惯。
--Socket消息--
function Network.OnSocket(key, data)
Event.Brocast(tostring(key), data);
end
因为在开始添加了监听,所以当c#掉上面这个Lua函数的时候,它就只需要广播一下消息来了,就好了,会有相应的监听函数去处理,那我们这个二进制的消息根据监听代码可以知道,应该是 function Network.OnLogin(buffer),为啥叫OnLogin,其实应该叫OnData是吧,都行吧,我这个人随意,我的框架场景名也叫Login,主要是配合它,Login场景自然要有Login消息数据了,您可以随意改~
在这个函数里面,通过一个define.lua中定义的全局变量TestProtoType,来确定当前框架用那种协议测试。
TestProtoType = ProtocalType.BINARY;
BINARY = 0, PB_LUA = 1, PBC = 2, SPROTO = 3,
我们看到二进制的数据传递给了TestLoginBinary函数:this.TestLoginBinary(buffer);
那这个函数当中其实还有一个区分协议的依据,就要根据读取到的一个字节(下面代码中的protocal)来区分:
function Network.TestLoginBinary(buffer)
local protocal = buffer:ReadByte();
local str = buffer:ReadString();
log('TestLoginBinary: protocal:>'..protocal..' str:>'..str);
end
开头这篇较长,后面的3种就不重复这些了。
借楼层更新-----------------------------------------------------------------------------------------
说下pblua,上面基础性的消息流程基本上已经都走通了,就不重复了,不明白的把上面帖子看明白。这篇帖子直接讲下protobuf_lua_gen
(2)protobuf_lua_gen:ULUA支持它也很早了,不过当时的ULUA还不是很成熟,蒙哥的csotolua还在发展过程当中,让ULUA支持它的应该是群里面的大神: Chiuan大C,后来教给我,当时需要修改很多地方,对于网络协议来说无非就是“进出”操作,protobuf_lua_gen在lua中序列化出来的字符串,local msg = login:SerializeToString(); 这个msg其实是个c语言的char*,这是我发现的,因为直接不能传给C#,所以需要在ULUA接收、通讯的地方,一个是需要将byte[]通过CopyMemory出来,另一端从c#拿到byte[]需要通过pushlstring将数据压进去,这样才完成了一次echo操作,当时真的是很痛苦的经历。幸好蒙哥csotolua有了LuaStringBuffer类,这个类没有物理文件,在uLua/Source/Base/LuaWrap.cs中,需要的同学可以看下这个文件。它简化了这种操作使char*传到c#这边就变成了LuaStringBuffer对象,我们再从它里面拿到byte[]类型的成员buffer,就省去了之前好多繁杂的互传操作。好了,我们看下Lua怎么发送这种类型的数据给服务器端,上PromptCtrl.lua中的代码:
--测试发送PBLUA--
function PromptCtrl.TestSendPblua()
local login = login_pb.LoginRequest();
login.id = 2000;
login.name = 'game';
login.email = 'jarjin@163.com';
local msg = login:SerializeToString();
----------------------------------------------------------------
local buffer = ByteBuffer.New();
buffer:WriteShort(Login);
buffer:WriteByte(ProtocalType.PB_LUA);
buffer:WriteBuffer(msg);
NetManager:SendMessage(buffer);
end
新版框架唯一需要说明的是,在ByteBuffer类中,我新增加了一个函数WriteBuffer,它实际上接收的就是pblua通过SerializeToString序列化出来的char*,并且将它传递给c#层,到了C#层的ByteBuffer.cs类中,我们可以看到它的实现:
//读buffer
public void WriteBuffer(LuaStringBuffer strBuffer) {
WriteBytes(strBuffer.buffer);
}
//写buffer
public LuaStringBuffer ReadBuffer() {
byte[] bytes = ReadBytes();
return new LuaStringBuffer(bytes);
}
很简洁吧,直接将byte[]类型的buffer成员写到字节数组里面去,然后整个类通过ToArray统一变成byte[],发送给服务器端。服务器也是按位读出该有的数据,然后将数据传回来,然后通过上个帖子的消息传递流程走到Lua的Network模块里面,然后进行解析:
function Network.TestLoginPblua(buffer)
local protocal = buffer:ReadByte();
local data = buffer:ReadBuffer();
local msg = login_pb.LoginResponse();
msg:ParseFromString(data);
log('TestLoginPblua: protocal:>'..protocal..' msg:>'..msg.id);
end
这个函数里面,也会相对应的增加了ReadBuffer的函数,也就是将byte[]包装成了LuaStringBuffer,然后Post给Lua就变成了pblua需要的char*,然后将其msg:ParseFromString(data);解析出来,就变成我们最终需要的Lua的Table格式,便可直接访问里面的数据成员,如msg.id了。这个流程操作也就顺利完成了。
pblua使用的是将protobuf协议文件编码成lua文件,然后在lua程序中require进来,赋值,序列化,发送。那它的编码工具,ulua.org提供了测试版,因为protobuf用的2.4.1老版本。测试可以,使用可以用心版本,下载地址为:http://www.ulua.org/download.html
protobuf-2.4.1.zip (d:/protobuf-2.4.1)
protoc-gen-lua.zip (d:/protoc-gen-lua)
请按照给定的路径放置,然后启动一个命令行到D:\protobuf-2.4.1\python目录下,(确保已安装python 2.7.3,并且bin目录添加到环境变量path中)输入下面命令:
python setup.py build
python setup.py install
编译:uLua/Editor/Packager.cs里面BuildProtobufFile函数。撑不下了,待续!
借楼层更新-----------------------------------------------------------------------------------------
上面还是说的多了,一个帖子没有撑下,这个帖子就不废话了,看来装逼是有代价的,赶快写完去LOL才是王道。
(3)PBC:它是云风大神早期的一个对protobuf的解析库,相对于protobuf_lua_gen来说,不需要生成巨多的lua协议描述文件,可以直接读取protobuf官方代码编译出来的protoc.exe生成的pb二进制文件,简洁效率还高。所以我们的项目也采用了pbc作为我们的主要通讯协议层。pbc有两种解析模式:(A)直接读取二进制的pb文件,从里面获取协议描述信息,(B)通过lpeg直接解析协议描述文件。应该是前者效率高于后者。
顺便在解析代码之前说下pbc专有的东西,很多人不知道怎么编译pb二进制文件,其实挺简单的,到官网去下载protobuf的c源码,编译出protoc.exe来,然后通过下面的命令行编译出pb文件:
protoc.exe -o目标文件 源文件
挺简单的吧?我协议的用的版本较老了2.4.1吧,如果为了测试可以去ulua.org去下载测试:http://www.ulua.org/download.html
protobuf-2.4.1.zip (d:/protobuf-2.4.1),对了顺便说下,使用pblua也需要它的源码编译出来的protoc.exe
开始看代码,继续打开lua/Controller/PromptCtrl.lua文件,看下怎么封装pbc的:
--测试发送PBC--
function PromptCtrl.TestSendPbc()
local path = Util.DataPath.."lua/3rd/pbc/addressbook.pb";
local addr = io.open(path, "rb")
local buffer = addr:read "*a"
addr:close()
protobuf.register(buffer)
local addressbook = {
name = "Alice",
id = 12345,
phone = {
{ number = "1301234567" },
{ number = "87654321", type = "WORK" },
}
}
local code = protobuf.encode("tutorial.Person", addressbook)
----------------------------------------------------------------
local buffer = ByteBuffer.New();
buffer:WriteShort(Login);
buffer:WriteByte(ProtocalType.PBC);
buffer:WriteBuffer(code);
NetManager:SendMessage(buffer);
end
这是直接读取pb二进制文件,直接用lpeg解析描述文件的例子,我没提供,你需要到pbc源码里面去找。流程是,打开pb文件-》注册描述数据结构-》填充数据-》编码,还是很清晰的逻辑。解码就不用我说了吧。直接打开Network.lua查看下面代码:
--PBC登录--
function Network.TestLoginPbc(buffer)
local protocal = buffer:ReadByte();
local data = buffer:ReadBuffer();
local path = Util.DataPath.."lua/3rd/pbc/addressbook.pb";
local addr = io.open(path, "rb")
local buffer = addr:read "*a"
addr:close()
protobuf.register(buffer)
local decode = protobuf.decode("tutorial.Person" , data)
print(decode.name)
print(decode.id)
for _,v in ipairs(decode.phone) do
print("\t"..v.number, v.type)
end
log('TestLoginPbc: protocal:>'..protocal);
end
使用pbc有个非常重要的一个问题,我不知道应不应该定位为bug,至少“云风大神”认为是个问题,但是不想改,我这里也附上解决方案,假如你有这么个消息,里面有个数组类型的repeat类型的数据字段,服务器连续发给你2次这个消息,而且里面一个成员字段的值都没有的话,pbc这两次给你的结构table是同一个内存指针地址,而不是每次都新分配的table。解决方案:你在接收这个数据之前,自己提前将数据表结构布局好,至少被copy的数据table要有个自己新建的空表{},才能导致数据不会串。如下:
local data["aaa"] = {};
data["aaa"] = buffer.data;
下一篇接着说spoto,待续...
从接触PureMvc的历程来看,显示惊喜,在讨厌,再到喜欢~
借楼层更新-----------------------------------------------------------------------------------------
终于到了最后一个sproto了,这次可以少废话点了吧,我觉得可以,为啥?没怎么用过,哈哈经验不足,就简单描述下echo流程,今天上午的帖子就不写了。下午补些知识点帖子。
(4)sproto:这个是云风大神的新作品,号称比pbc包小、效率高效。因为这是大神自己创建出来的协议格式,不是protobuf的解析库。目前我知道的朋友们用它的也开始多起来,使用体验如何?我不清楚,没怎么用过,希望你们使用完了,告诉我下。
咱们继续打开lua/Controller/PromptCtrl.lua,看下里面关于sproto如何封包,发送出去的代码:
--测试发送SPROTO--
function PromptCtrl.TestSendSproto()
local sp = sproto.parse [[
--此处省略sproto描述字符串,帖子有字数限制,请直接参考lua代码--
}
local code = sp:encode("AddressBook", ab)
----------------------------------------------------------------
local buffer = ByteBuffer.New();
buffer:WriteShort(Login);
buffer:WriteByte(ProtocalType.SPROTO);
buffer:WriteBuffer(code);
NetManager:SendMessage(buffer);
end
其实还是挺简单的,思路如pbc的一样,封包、发出去。然后我们继续看下Network里面如何解析的:
--SPROTO登录--
function Network.TestLoginSproto(buffer)
local protocal = buffer:ReadByte();
local code = buffer:ReadBuffer();
local sp = sproto.parse [[
.Person {
name 0 : string
id 1 : integer
email 2 : string
.PhoneNumber {
number 0 : string
type 1 : integer
}
phone 3 : *PhoneNumber
}
.AddressBook {
person 0 : *Person(id)
others 1 : *Person
}
]]
local addr = sp:decode("AddressBook", code)
print_r(addr)
log('TestLoginSproto: protocal:>'..protocal);
end
解析消息也超简单,也是decode,然后就是你想要的lua表数据结构。
终于废话完了,希望协议的系列帖子能帮你解决你手头的需求。。。