分享

ToLua SimpleFramework NGUI/UGUI基础知识[4]

 kiki的号 2017-04-17

ToLua SimpleFramework NGUI/UGUI基础知识[4]

原文地址:http://doc./default.asp?cateID=4

视频地址:http://pan.baidu.com/s/1gd8fG4N

周六早晨,咱接着更新,这次咱们说下SimpleFramework使用的4种网络协议层:bytebufferprotobuf_lua_genpbcsproto

1bytebuffer:这个只是框架里面的一个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我的ffffQuuu");

    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给了LuaNetwork模块,(其实如果你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

 

2protobuf_lua_genULUA支持它也很早了,不过当时的ULUA还不是很成熟,蒙哥的csotolua还在发展过程当中,让ULUA支持它的应该是群里面的大神: ChiuanC,后来教给我,当时需要修改很多地方,对于网络协议来说无非就是进出操作,protobuf_lua_genlua中序列化出来的字符串,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[],发送给服务器端。服务器也是按位读出该有的数据,然后将数据传回来,然后通过上个帖子的消息传递流程走到LuaNetwork模块里面,然后进行解析:

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,然后PostLua就变成了pblua需要的char*,然后将其msg:ParseFromString(data);解析出来,就变成我们最终需要的LuaTable格式,便可直接访问里面的数据成员,如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才是王道。

3PBC:它是云风大神早期的一个对protobuf的解析库,相对于protobuf_lua_gen来说,不需要生成巨多的lua协议描述文件,可以直接读取protobuf官方代码编译出来的protoc.exe生成的pb二进制文件,简洁效率还高。所以我们的项目也采用了pbc作为我们的主要通讯协议层。pbc有两种解析模式:(A)直接读取二进制的pb文件,从里面获取协议描述信息,(B)通过lpeg直接解析协议描述文件。应该是前者效率高于后者。

顺便在解析代码之前说下pbc专有的东西,很多人不知道怎么编译pb二进制文件,其实挺简单的,到官网去下载protobufc源码,编译出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流程,今天上午的帖子就不写了。下午补些知识点帖子。

 

4sproto:这个是云风大神的新作品,号称比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表数据结构。

终于废话完了,希望协议的系列帖子能帮你解决你手头的需求。。。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多