标签:
众所周知,服务器的一个主要功能是对数据进行存储,有些游戏服务器直接将服务器引擎部分分成了网络和数据库两大引擎(PS:网易貌似就是这么搞的),对于数据库操作,很多都是重复性的代码,如果对于接口调用不熟悉的人写出来的代码,容易出错,而且重复性的劳动,意义不大。
在此我将以KBE引擎的数据库模块为模板,对数据库的一些操作的封装进行讨论,最终要了解服务器程序如果通过只要编写一些比如xml的配置,底层就可以直接对这些需要的内容进行存储。对于底层存储的具体机制在此不作讨论,以后有机会再进行分析啊。
简单说明一下,编写KBE游戏服务器是不需要写SQL语句的一些调用,不要进行数据库的一些操作,比如我们以前要对一张表进行操作,通常会用比如oss数据流,输入一些update,delete等操作,从而对数据库表进行操作,这些代码比较类似。
要进行分析,先抛出两个问题:
1: 服务器底层是如何知道服务器程序员需要写入什么表?
这个问题包含两个问题,
a: 如果该表以前已经定义了怎么办?
b: 该表没有定义,是新增的怎么办?
2:服务器底层如何知道要写入哪些属性?
之所以会有这个问题是因为有些属性不是需要存入数据库的,只是作为临时属性读取就可以了
KBE中需要存储的东西称之为 entity ,在脚本目录 scripts 根下面会有一个名为 entities.xml 的文件,这个是服务器启动的时候会自动加载,里面定义了那些 entity 是需要存储的,下面是rpg demo 的entities 的配置代码:
- <root>
- <Account/>
- <Avatar/>
- <Spaces/>
- <Space/>
- <SpaceCopy/>
- <SpaceFightCopy/>
- <SpawnPoint/>
- <Monster/>
- <NPC/>
- <Gate/>
- <AccountRoom/>
- </root>
复制代码
里面定义的比如Account 是玩家账号相关的数据,Avatar 是玩家所在space的一些数据,在同级目录下面,会有一个 entity_defs 文件夹,这里面是 entity 的具体定义,我们以 Account为例子,下面是 Account.def 的源码:
- <root>
- <Properties>
- <characters>
- <Type> AVATAR_INFOS_LIST </Type>
- <Flags> BASE </Flags>
- <Default> </Default>
- <Persistent> true </Persistent>
- </characters>
- <lastSelCharacter>
- <Type> DBID </Type>
- <Flags> BASE_AND_CLIENT </Flags>
- <Default> 0 </Default>
- <Persistent> true </Persistent>
- </lastSelCharacter>
- <accountName>
- <Type> UNICODE </Type>
- <Flags> BASE </Flags>
- <Persistent> true </Persistent>
- <DatabaseLength> 100 </DatabaseLength>
- <Default> kbengine </Default>
- <Identifier> true </Identifier>
- </accountName>
-
- <password>
- <Type> STRING </Type>
- <Flags> BASE </Flags>
- <Default> </Default>
- <Persistent> true </Persistent>
- </password>
-
- <activeCharacter>
- <Type> MAILBOX </Type>
- <Flags> BASE </Flags>
- </activeCharacter>
-
- <lastClientIpAddr>
- <Type> UINT32 </Type>
- <Flags> BASE </Flags>
- <Default> 0 </Default>
- </lastClientIpAddr>
- </Properties>
- </root>
复制代码
里面的第三级key 是表明了该entity 在数据库中的一个属性,比如其中的 characters ,type是该字段的类型,会对于到CPP中的类型,比如OBJECT_ID是对应的INT32 ,是在 alias.xml 进行对应的,而 Persistent 字段表明了该属性是不是要进行存储,如果其值
是true表示该字段是需要进行存储的。(PS:上面的entity只是截取了和数据库相关的一些内容,还有该entity会有对应的方法,这些就不写啦,因为和这次的分析无关)下面是服务器对这些entity的xml 进行读取,解析的过程源码,读取 entities.xml的读取:
- bool EntityDef::initialize(std::vector<PyTypeObject*>& scriptBaseTypes,
- COMPONENT_TYPE loadComponentType)
- // 开始遍历所有的entity节点
- XML_FOR_BEGIN(node)
- {
- std::string moduleName = xml.get()->getKey(node);
- __scriptTypeMappingUType[moduleName] = utype;
- ScriptDefModule* scriptModule = new ScriptDefModule(moduleName, utype++);
- EntityDef::__scriptModules.push_back(scriptModule);
- std::string deffile = defFilePath + moduleName + ".def";
- SmartPointer<XML> defxml(new XML());
- if(!defxml->openSection(deffile.c_str()))
- return false;
- TiXmlNode* defNode = defxml->getRootNode();
- if(defNode == NULL)
- {
- // root节点下没有子节点了
- continue;
- }
- // 加载def文件中的定义
- if(!loadDefInfo(defFilePath, moduleName, defxml.get(), defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::initialize: failed to load entity:{} parentClass.\n",
- moduleName.c_str()));
- return false;
- }
-
- // 尝试在主entity文件中加载detailLevel数据
- if(!loadDetailLevelInfo(defFilePath, moduleName, defxml.get(), defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::initialize: failed to load entity:{} DetailLevelInfo.\n",
- moduleName.c_str()));
- return false;
- }
- scriptModule->onLoaded();
- }
复制代码
下面是对 entity的定义文件 def 进行解析的过程:
- bool EntityDef::loadDefInfo(const std::string& defFilePath,
- const std::string& moduleName,
- XML* defxml,
- TiXmlNode* defNode,
- ScriptDefModule* scriptModule)
- {
- if(!loadAllDefDescriptions(moduleName, defxml, defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::loadDefInfo: failed to loadAllDefDescription(), entity:{}\n",
- moduleName.c_str()));
- return false;
- }
-
- // 遍历所有的interface, 并将他们的方法和属性加入到模块中
- if(!loadInterfaces(defFilePath, moduleName, defxml, defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::loadDefInfo: failed to load entity:{} interface.\n",
- moduleName.c_str()));
- return false;
- }
-
- // 加载父类所有的内容
- if(!loadParentClass(defFilePath, moduleName, defxml, defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::loadDefInfo: failed to load entity:{} parentClass.\n",
- moduleName.c_str()));
- return false;
- }
- // 尝试加载detailLevel数据
- if(!loadDetailLevelInfo(defFilePath, moduleName, defxml, defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::loadDefInfo: failed to load entity:{} DetailLevelInfo.\n",
- moduleName.c_str()));
- return false;
- }
- // 尝试加载VolatileInfo数据
- if(!loadVolatileInfo(defFilePath, moduleName, defxml, defNode, scriptModule))
- {
- ERROR_MSG(fmt::format("EntityDef::loadDefInfo: failed to load entity:{} VolatileInfo.\n",
- moduleName.c_str()));
- return false;
- }
-
- scriptModule->autoMatchCompOwn();
- return true;
- }
复制代码
上面都是截取的一些代码,可以参考源代码的,到这里,服务器已经知道了哪些属性是需要存储的了,至于如何存储,何时存储,以及存储遇到问题的解决方式,是在数据库模块db_interface进行的。
下面是数据库模块操作函数的一些声明:
- /**
- 同步entity表到数据库中
- */
- virtual bool syncToDB(DBInterface* dbi);
- virtual bool syncIndexToDB(DBInterface* dbi){ return true; }
- virtual bool logEntity(DBInterface * dbi, const char* ip, uint32 port, DBID dbid,
- COMPONENT_ID componentID, ENTITY_ID entityID, ENTITY_SCRIPT_UID entityType);
- virtual bool queryEntity(DBInterface * dbi, DBID dbid, EntityLog& entitylog, ENTITY_SCRIPT_UID entityType);
- virtual bool eraseEntityLog(DBInterface * dbi, DBID dbid, ENTITY_SCRIPT_UID entityType);
复制代码
里面会有读取这些属性在数据库中的数据,至于如果数据字段不需要存储了,已经新增的情况,都会在同步操作 syncToDB中进行,这里暂时不作详细分析,下次再分享,本人对数据库操作部分也是有浓厚兴趣的。
关于kbengine存储的封装
标签:
原文地址:http://www.cnblogs.com/coder1980/p/4769556.html