ToLua SimpleFramework NGUI/UGUI基础知识[4]原文地址:http://doc./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进来,赋值,序列化,发送。那它的编码工具,提供了测试版,因为protobuf用的2.4.1老版本。测试可以,使用可以用心版本,下载地址为:http://www./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吧,如果为了测试可以去去下载测试:http://www./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表数据结构。 终于废话完了,希望协议的系列帖子能帮你解决你手头的需求。。。
|
|
来自: kiki的号 > 《热更新(lua)》