SimpleFramework NGUI/UGUI基础知识[4]

借楼层更新-----------------------------------------------------------------------------------------
周六早晨,咱接着更新,这次咱们说下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
请按照给定的路径放置,然后启动一个命令行到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表数据结构。
终于废话完了,希望协议的系列帖子能帮你解决你手头的需求。。。


[本日志由 admin 于 2015-09-03 01:00 PM 更新]
上一篇: SimpleFramework NGUI/UGUI基础知识[3]
下一篇: uLua1.20/1.21关于动态Wrap的使用必读!!!
文章来自: 本站原创
引用通告: 查看所有引用 | 我要引用此文章
Tags:
相关日志:
评论: 1 | 引用: 0 | 查看次数: -
greenboy[2016-02-25 05:07 PM | | Mail To:360315247@qq.com | 58.30.38.100 | del | 回复回复]
使用NGUI的Ulua框架进行热更新 发现可以在不更改任何设置的情况下 打包正常  但是针对iOS平台 进行打包异常 不知道楼主 打包是否正常?我使用的环境是 unity 5.3.1f  

后我将Player Setting 里面的 Scripting Backend 更改成:LL2CPP
后 可以正常build工程 但是在运行xcode进行编译的时候一直提醒说是 缺少相应的armv7 或者 Arm64支持 相应的.a文件也都存在 一直无法正常打包到iOS平台 旺指点下 谢谢
发表评论
昵 称:
密 码: 游客发言不需要密码.
邮 箱: 邮件地址支持Gravatar头像,邮箱地址不会公开.
网 址: 输入网址便于回访.
内 容:
验证码:
选 项:
虽然发表评论不用注册,但是为了保护您的发言权,建议您注册帐号.
字数限制 1000 字 | UBB代码 开启 | [img]标签 关闭