Lua自适应协议解析器开发记录(一)

更新时间:2023-07-19 04:27:00 阅读: 评论:0

Lua⾃适应协议解析器开发记录(⼀)
  在游戏项⽬开发中, 需要涉及协议的定义及解析, 例如服务端使⽤c++底层, 前端使⽤ as进⾏ flash显⽰, 前后段数据通信采⽤ socket, 这就需要协议的定制了. 服务端使⽤ c++ 做底层⽹络维护, 搭配 lua 脚本处理逻辑和协议解析处理; 使⽤这种⽅式的好处时, 指定新协议或修改时,⽆需重新编译 C++ 的底层, 只需要修改 lua 脚本, 并重启服务端程序或重新加载 lua脚本即可. 唯⼀的问题时, 当前项⽬在⽴项时, 被设计的不友好, 每个模块分配不同同事开发, 每个同事都需要了解协议的格式, 例如"交换背包内两个物品时", lua 这边需要有如
function ChangePos( ur, sockRawData )
千山万水简谱
local operation = CPar( sockRawData, "i:operation" )
if operation == 1then--删除物品
local deleteItemID = CPar( sockRawData, "i:itemid" )
... delete item
elif operation == 2then--交换位置
local firstItemID, condItemID = CPar( sockRawData, "i:first|i:cond" )
... change firstItemID and condItemID
el ...
...
end
end
即负责 ChangePos() 模块的开发⼈员, 必须直到 CPar() 的使⽤⽅式, 以及协议的构成 : i:packetid|i:operation|?:?
理想的的 ChangePos() 应该能够这样
function ChangePos( ur, readable )
if readable.option == 1then
delete ( readable.itemid)
elif readable.operation == 2then
change( readable.first, d )
el ...
...
end
end
即⽆需在关注 socket 是如何构成原始数据的, 如何进⾏进⾏解析; 只需要直到逻辑数据的构成, 例如 ca option == 1 , 后⾯的第⼀个数据就是要删除的 itemid, ca option == 2 , 后⾯的两个数据分别是将要进⾏交换的物品 first 和 cond 更多⼯作放在业务逻辑上.
确实, 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( ur, readable )
if readable.option == 1then
delete ( readable.itemid)
elif readable.operation == 2then
change( readable.first, d )
el ...
...
end
end
这种⽅式进⾏开发, 必须有⼀个机制, 能够将原始的⽹络数据解析成 readable 的 lua-table 虽然, 在 Love2D 的⽹络通信中, 我使⽤的是⾃带的 socket 库: require( "socket"), 并且接收到的数据也是字符串数据类型(虽然游戏字符⽆法解析), 即便也能够使⽤ lua 语⾔进⾏解析; 为了练习, 我还是将⽹络数据定义成  urdata , 以备后来前端修改为 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:cond}}", --背包相关协议
[1002] = ...
...
}
function Underlying_on_network_receive_data( ur, sockRawUrData )
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( ur, 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},
            因为 {} ⾃带"块"(及分隔)的意义
控制字符( 下⾯介绍的 o: 和 r:) 后⾯不需要该分隔符号, 例如 o:option|{{i:goldAdd|i:copperAdd}}
            会被整理成 o:option{{i:goldAdd|i:copperAdd}}  因为 {} ⾃带"块"(及分隔)的意义
o:                  开关标记,开关数值范围为 [1,...) ⼤于0的连续整数,后⾯会附带⼀组 {} 标记的数据块列表,举例来说
o:option{ {i:goldAdd|i:copperAdd},{s:newname},{i:hpAdd},{r:mpAdjustList{i:adj}} }
switch option :
ca 1: 后⾯有两个数据            i:goldAdd|i:copperAdd
⽣成的lua 结构:
{
option = 1,
option_list =
{
goldAdd = ?,
copperAdd = ?,
},
}
ca 2: 后⾯有⼀个数据            s:newname
⽣成的lua 结构:
{
option = 2,
摇滚英文option_list =
{
newname = ?,
},
}
ca 3:后⾯有⼀个数据            i:hpAdd
⽣成的lua 结构:
{
option = 3,
option_list =
{
hpAdd = ?,
},
}
ca 4:后⾯是⼀个循环数据块    {r:mpAdjustList{i:adj} }
⽣成的lua 结构:
{
option = 4,
option_list =
{
???
},
}
即当前 option 的值,附带列表的值
以_list结尾
r:                    表⽰后⾯多少相同模式的数据,举例来说
{r:mpAdjustList{i:adj} }
此数据块有 mpAdjustList 个循环数据,每个循环内有⼀个数据 i:adj
--能够被忽略,但为了易查看的字符
ignored characters:        \t\r\n,
协议例⼦:
"i:packetid|i:urid|i:urname|i:gold|i:copper|i:hp|i:mp|o:switch{{i:goldAdd|i:copperAdd},{s:newname},{i:hpAdd},{r:mpAdjustList{i:adj}} }"
可转化为层次结构:
"i:packetid|i:urid|i:urname|i:gold|i:copper|i:hp|i:mp|o:switch \
{ \
{i:goldAdd|i:copperAdd}, \
{s:newname}, \
{i:hpAdd}, \
{r:mpAdjustList \
{i:adj} \
} \
}"
介绍完协议规则后, 下⾯开始 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 ...");
return0;
}
回南天怎么防潮
struct luaL_reg protocalFunctions[] =
{
{ "unpack", unpack},
{ 0, 0}
};
extern"C"int luaopen_protocal( lua_State* L)
{
luaL_register( L, "protocal", protocalFunctions);
return1;
}
⽣成 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 放在 同⽬录内)
使⽤时:
--test.lua
require( "protocal" )
持之以恒的名言
protocal.unpack() --将会输出: hello, pig! ready to unpack ...
到此,使⽤ vs2010 导出 dll 给 lua 使⽤的框架暂且搭好了
备注:
1.这是线程不安全的,因为使⽤ strtok 进⾏切割
2.未考虑⼤端⼩端问题
3.字符创采⽤ length-bad ,并且给 sizeof(前导长度) == sizeof(int)
...后续: 具体 unpack() 函数过程

本文发布于:2023-07-19 04:27:00,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1087273.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:协议   数据   需要   解析
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图