在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
在游戏项目开发中, 需要涉及协议的定义及解析, 例如服务端使用c++底层, 前端使用 as进行 flash显示, 前后段数据通信采用 socket, 这就需要协议的定制了. 服务端使用 c++ 做底层网络维护, 搭配 lua 脚本处理逻辑 和 协议解析处理; 使用这种方式的好处时, 指定新协议或修改时, 无需重新编译 C++ 的底层, 只需要修改 lua 脚本, 并重启 服务端程序或 重新加载 lua脚本即可. 唯一的问题时, 当前项目在立项时, 被设计的不友好, 每个模块分配不同同事开发, 每个同事都需要了解协议的格式, 例如"交换背包内两个物品时", lua 这边需要有如 function ChangePos( user, sockRawData ) local operation = CParse( sockRawData, "i:operation" ) if operation == 1 then --删除物品 local deleteItemID = CParse( sockRawData, "i:itemid" ) ... delete item elseif operation == 2 then --交换位置 local firstItemID, secondItemID = CParse( sockRawData, "i:first|i:second" ) ... change firstItemID and secondItemID else ... ... end end
即负责 ChangePos() 模块的开发人员, 必须直到 CParse() 的使用方式, 以及 协议的构成 : i:packetid|i:operation|?:? 理想的的 ChangePos() 应该能够这样 function ChangePos( user, readable ) if readable.option == 1 then delete ( readable.itemid) elseif readable.operation == 2 then change( readable.first, readable.second ) else ... ... end end
即无需在关注 socket 是如何构成原始数据的, 如何进行进行解析; 只需要直到 逻辑数据的构成, 例如 case option == 1 , 后面的 第一个数据就是 要删除的 itemid, case option == 2 , 后面的 两个数据分别是 将要进行交换的 物品 first 和 second 更多工作放在 业务逻辑上. 确实, json, messagepack, protobuf 都能够实现这种需求; 但目前, 我只在 c++ 底层实现了 protobuf 的使用, 并且想再 用现有的 protobuf-for-lua 按照 个lua 版本的 protobuf, 但是太麻烦了: 还需要 安装 python, 等其他库; 并且 c++ 的protobuf 是 通过 预先使用 protc 生成 各个协议的 静态类, 再拷贝到 实际项目中进行引入的 但(我的浅薄经验来看) 我人 希望很多不是很底层的代码都能够自己掌控, 并且 能够使用设计模式 设计类 , 例如 抽象工厂模式 管理 各个协议等等. 好吧, 服务端用 C++ 开发, 确定的协议规则有: 1.每个协议数据包分为两部分 包头(指代包体的长度) 和 包体 2.数据类型只有两种: int32 和 带前导长度的字符串(无\0) 3.没有数据压缩 在服务端, 不适用 线程的 protobuf 等应用库, 使用最简单的数据发送方式; 在前端, 使用 Love2D , 使用 lua 脚本进行编写, 解析协议数据 , 也不使用 protobuf 为了在 Love2D 前端能够使用 : function ChangePos( user, readable ) if readable.option == 1 then delete ( readable.itemid) elseif readable.operation == 2 then change( readable.first, readable.second ) else ... ... end end
这种方式进行开发, 必须有一个机制, 能够将 原始的网络数据解析成 readable 的 lua-table 虽然, 在 Love2D 的网络通信中, 我使用的是 自带的 socket 库: require( "socket"), 并且接收到的数据也是 字符串数据类型(虽然游戏字符无法解析), 即便也能够使用 lua 语言进行解析; 为了练习, 我还是将 网络数据 定义成 userdata , 以备后来 前端修改为 C++; 呃, 虽然 Love2D 也是C++编写的, 能够重新编译或者修改 Love2D 源代码, 生成自己的 Love2D; 我不要, 我当前的目标是快速的开发前端, 毕竟服务端设计才是我应该关注的重点; 于是我现在使用的Love2D 是 exe 已编译成功的. 既然, 前端的 "使用C++编写的" Love2D 引擎不能被修改, 又不使用 Love2D 的 Lua 进行协议解析, 只能是 用 C++ 开发一个 protocal.dll 供 Love2D 的 lua 脚本使用 rquire( "protocal" ) 预期的 Love2D 前端使用 效果将如: packet_format_list = { [1001] = "i:packetid|i:option{{i:itemid}{i:first}{i:second}}", --背包相关协议 [1002] = ... ... } function Underlying_on_network_receive_data( user, sockRawUserData ) local packet_id = nil local packet_format = nil ... --假设经过上面处理, 确定该协议 是 1001 packet_id = 1001 ... local packet_format = packet_format[ packet_id] local protocal = require( "protocal" ) local readable = protocal.unpack( sockRawData, packet_format_list ) --因为已经假设该协议是 1001 的背包相关协议 ChangePos( user, readable ) end
所以, 本篇随笔的 主要工作就是 protocal.dll 的开发. 为了介绍 protocal.unpack() 函数的实际工作过程, 有必要先规定 通信协议 及 协议的编写, 其中的 三点已在前面介绍: --数据规则 1.每个协议数据包分为两部分 包头(指代包体的长度) 和 包体 2.数据类型只有两种: int32 和 带前导长度的字符串(无\0) 3.没有数据压缩 --协议编写规则 关键字符/或短语: i: int32 例如 i:goldAdd s: 带长度的字符创 例如 s:newname {} 数据块 例如 {i:goldAdd|i:copperAdd} | 分隔符 例如分隔不同的字段 i:goldAdd|i:copperAdd 数据块之间不需要该分隔符, 例如{i:goldAdd|i:copperAdd}|{s:newname} 会被自动整理成 {i:goldAdd|i:copperAdd}{s:newname},
介绍完协议规则后, 下面开始 protocal.dll 的实际开发 首先, 使用 vs2010 创建 protocal.dll extern "C" { #include "lua/lua.h" #include "lua/lualib.h" #include "lua/lauxlib.h" } #include <Windows.h> #include <WinCrypt.h> extern "C" int unpack( lua_State* L) { printf( "hello, pig! ready to unpack ..."); return 0; } struct luaL_reg protocalFunctions[] = { { "unpack", unpack}, { 0, 0} }; extern "C" int luaopen_protocal( lua_State* L) { luaL_register( L, "protocal", protocalFunctions); return 1; } 生成 protocal.dll
注意, 当前版本的lua 我遇到一个问题, 如果将 protocal.dll 改名为 otherName.dll 在lua 进行 require( "otherName") 时会报错:找不到指定过的模块 解决方案: 1. dll 导出给 lua 的模块名:extern "C" int luaopen_MYLIBRARY( lua_State* L) 2. vs 生成的 MYLIBRARY.dll 3. require( "MYLIBRARY" )
这三者的 MYLIBRARY 要一致, 本项目为: 1. dll 导出给 lua 的模块名:extern "C" int luaopen_protocal( lua_State* L) 2. vs 生成的 protocal.dll 3. require( "protocal" ) 另外对于vs2010 还要设置 "模块定义文件" < "输入" < "链接器" < "配置属性" 添加 .def 文件: 例如: EXPORTS luaopen_netpack (最后, 使用 Love2D 时, 需要将 生成的 protocal.dll 放在love.exe 同目录内) 使用时: --test.lua require( "protocal" ) protocal.unpack() --将会输出: hello, pig! ready to unpack ...
到此, 使用 vs2010 导出 dll 给 lua 使用的框架暂且搭好了
备注: 1.这是线程不安全的, 因为使用 strtok 进行切割 2.未考虑大端小端问题 3.字符创采用 length-based , 并且给 sizeof(前导长度) == sizeof(int) ...后续: 具体 unpack() 函数过程 |
请发表评论