zoukankan      html  css  js  c++  java
  • 天龙八部服务器端共享内存的设计

    一、服务器构架

    一个天龙八部游戏区,主要服务器部署情况如下图所示:

    实 际部署可能有所不同。区角色数据库可以安装到Machine4,那么一个区有5台物理机器。LoginServer和WorldServer、 CharacterDB、BillingServer有连接。WorldServer和各个GameServer有连接。ShareMemory和 CharacterDB有连接。

    一台物理机器上,会启动一个ShareMemory进程和一个服务器进程,服务器进程有世界服务器和游戏服务器。天龙八部的世界是ZoneBase的,一个游戏服务器服务启动多个线程,每个线程服务若干个场景。

    在ShareMemory进程、WorldServer和GameServer进程,都需要对多种共享内存池进行初始化,初始化传入的类型是不同的,类型定义如下:

    enum SMPOOL_TYPE

    {

    SMPT_SHAREMEM,

    SMPT_SERVER,

    SMPT_WORLD

    };

    共享内存池对象在ShareMemory进程中初始化时候,才会真正分配共享内存对象,并把池对象Attach到共享内存对象。在WorldServer和GameServer进程中,池对象只是获取共享内存对象并Attach。

    天龙八部使用的共享内存对象有:

    角色 SMUPool<HumanSMU> 类型ST_HUMAN_SMU;

    公会 SMUPool<GuildSMU> 类型ST_GUILD_SMU;

    邮件 SMUPool<MailSMU> 类型ST_MAIL_SMU;

    玩家商店 SMUPool<PlayerShopSM> 类型ST_PSHOP_SMU;

    道具序列号SMUPool<ItemSerialKeySMU> 类型ST_ITEMSERIAL_SMU;

    二、共享内存模块

    几个关键的类和结构定义如下图:

    ShareMemAO封装了系统共享内存API功能,提供了池对象Attach/Detach接口以及转储文件功能。

    SMUPool模板类是具体的池对象,天龙八部实现的池类型有5种,具体数据类型对应5个结构:HumanSMU、GuildSMU、MailSMU、PlayerShopSM和ItemSerialKeySMU。

    SMULogicManager模板类提供了对这些共享内存池对象进行管理的功能,如初始化、心跳、保存数据库、清理等操作。

    三、ShareMemory进程中共享内存模块的主要功能

    具体见ShareMemory.h/ShareMemory.cpp,该服务进程主要功能是:

    l 根据配置创建共享内存池对象(SMUPool)和池管理对象(SMULogicManager);

    l 对创建的内存池对象和池管理对象进行初始化,注意初始化传入的类型是SMPT_SHAREMEM;如果是这个类型,底层会分配具体的共享内存,而不是仅仅的Attach;

    l 对共享内存池管理对象进行更新(心跳),会进行数据库存储等操作,这里是保存数据库的唯一地方。那么数据从数据库的加载是在哪里?下面会介绍,是在LonginServer进程里面。

    关于数据库模块,顺带提一下,从代码看天龙居然用的还是ODBC接口,后来应该改了吧。

    四、WorldServer中的共享内存池对象

    具体见ShareMemManager.h/ShareMemManager.cpp。WorldServer中仅对两类池对象进行操作,他们是GuildSMU和MailSMU。

    extern SMUPool<GuildSMU> g_GuildSMUPool;

    extern SMUManager<GuildSMU> g_GuildSMUManager;

    extern SMUPool<MailSMU> g_MailSMUPool;

    extern SMUManager<MailSMU> g_MailSMUManager;

    两个共享内存池对象的初始化在World.cpp的BOOL World::NewStaticManager( )函数中,注意此处传入的类型是SMPT_WORLD。

    五、GameServer中的共享内存池对象

    具体见ShareMemManager.h/ShareMemManager.cpp。GameServer中对三类池对象进行操作,他们是HumanSMU、PlayerShopSM和ItemSerialKeySMU。

    SMUPool<HumanSMU> g_HumanSMUPool;

    SMUPool<PlayerShopSM> g_PlayerShopSMUPool;

    SMUPool<ItemSerialKeySMU> g_ItemSerialSMUPool;

    这些共享内存池对象的初始化在Server.cpp的BOOL Server::NewStaticManager( )函数中,注意此处传入的类型是SMPT_SERVER。

    六、样例分析——玩家角色数据的存储和共享

    服务器端玩家角色对于的类型是class Obj_Human,里面有角色数据库存储接口的定义:

    protected:

    //存放所有关于Obj_Human的、从数据库里读取的信息

    HumanDB m_DB;

    class HumanDB中拥有内存共享对象指针,以及角色需要存储的数据定义。

    private:

    //共享内存相关数据

    HumanSMU* m_HumanSMU; //共享内存数据

    HUMAN_DB_ATTR_FLAG* m_AttrFlag; //角色属性刷新控制数据

    private:

    _HUMAN_DB_LOAD* m_dbHuman ; //角色基本信息

    _BAG_DB_LOAD* m_dbBag ; //角色背包物品信息

    _EQUIP_DB_LOAD* m_dbEquip ; //角色装备信息

    _BANK_DB_LOAD* m_dbBank ; //角色银行物品信息

    _SKILL_DB_LOAD* m_dbSkill ; //角色身上拥有的技能信息

    _COOLDOWN_DB_LOAD_FOR_HUMAN* m_dbCooldown ; //角色身上的冷却信息

    _RELATION_DB_LOAD* m_dbRelation; //角色联系人(好友、黑名单)

    _ABILITY_DB_LOAD* m_dbAbility; //角色学会的生活技能信息以及配方表

    _XINFA_DB_LOAD* m_dbXinFa ; //角色学会的心法信息

    _IMPACT_DB_LOAD* m_dbImpact ; //角色身上所施加的附加效果信息

    _MISSION_DB_LOAD* m_dbMission; //任务列表

    _SETTING_DB_LOAD* m_dbSetting ; //设置数据

    _PRIVATE_INFO_DB_LOAD* m_dbPrivateInfo;//私人信息

    上面这些xxx_LOAD很眼熟,其实在struct HumanSMU里面有类似定义:

    struct HumanSMU

    {

    SMUHead m_SMUHead;

    HUMAN_DB_ATTR_FLAG m_AttrFlag; //角色属性标志位

    _HUMAN_DB_LOAD m_HumanSM ; //角色基本信息

    _BANK_DB_LOAD m_BankSM ; //角色银行物品信息

    _SKILL_DB_LOAD m_SkillSM ; //角色身上拥有的技能信息

    _COOLDOWN_DB_LOAD_FOR_HUMAN m_CooldownSM ; //角色身上的冷却信息

    _XINFA_DB_LOAD m_XinFaSM ; //角色学会的心法信息

    _IMPACT_DB_LOAD m_ImpactSM ; //角色身上所施加的附加效果信息

    _ABILITY_DB_LOAD m_AbilitySM; //角色学会的生活技能信息以及配方表

    _MISSION_DB_LOAD m_MissionSM; //任务列表

    _SETTING_DB_LOAD m_SettingSM; //任务列表

    _PET_DB_LIST_LOAD m_PetListSM; //宠物列表

    _BAG_DB_LOAD m_BagSM; //角色背包物品信息

    _EQUIP_DB_LOAD m_EquipSM; //角色装备信息

    _RELATION_DB_LOAD m_RelationSM; //角色联系人(好友、黑名单)

    _PRIVATE_INFO_DB_LOAD m_PrivateInfoSM;//私人信息

    };

    再看看HumanDB如何初始化的,看其构造函数(代码太多,删除了一些):

    HumanDB::HumanDB( )

    {

    //这里获取共享内存存储单元

    HumanSMU* pSMU = g_HumanSMUPool.NewObj();

    m_HumanSMU = pSMU;

    m_AttrRegSM = new HUMAN_DB_ATTR_REG;

    m_AttrReg = new HUMAN_DB_ATTR_REG;

    m_AttrFlag = new HUMAN_DB_ATTR_FLAG;

    m_dbHuman = new _HUMAN_DB_LOAD ;

    //…略过一些对象内存分配

    //属性表和DB数据的挂接,用于脏数据判断和数据位置对接

    _RegisterDBAttributes();

    //属性表和内存共享对象挂接,用于脏数据判断和数据位置对接

    _RegisterSMAttributes();

    }

    1. Human数据的加载是通过DBCharFullData进行的,检查了一下代码,加载的地方只有两个地方,一个是创建角色的消息响应,另外一个地方是角色登录消息响应,这些都是LonginServer(登录服务器)处理的。

    ServerLoginPacketsCLAskCreateCharHander.cpp

    ServerLoginPacketsWLRetCharLoginHandler.cpp

    角色数据被加载后,发给了WorldServer,又通过WorldServer发送给GameServer。

    2. GameServer的消息响应如下:

    UINT WGRetUserDataHandler::Execute( WGRetUserData* pPacket, Player* pPlayer )

    //这么大的数据包是通过网络发过来的

    pGamePlayer->InitHuman( pPacket->GetUserData(),UDR_USERDATA, pPacket->GetPlayerAge() ) ;

    //InitHuman里面对HumanDB数据进行赋值

    _OBJ_HUMAN_INIT initHuman;

    initHuman.m_pUseData = pData;

    m_pHuman->Init( &initHuman, age ) ;

    m_pHuman->ValidateShareMem(TRUE); //再把数据复制到内存共享单元里面

    3. HumanDB通过ValidateShareMem函数把数据复制到共享内存单元。

    VOID HumanDB::ValidateShareMem(BOOL bForceAll,BOOL& bUpdateAttr)

    4. 在Obj_Human的更新函数里面把更新后的数据复制到共享内存单元。

    BOOL Obj_Human::HeartBeat( UINT uTime )

    ValidateShareMem(FALSE); //最后更新数据到共享内存

    七、总结

    1. TL为什么要用共享内存

    因 为天龙八部的世界是ZoneBase的, 一个区有多台游戏服务器,一台游戏服务器服务多个场景,一台游戏服务器上有多个场景线程。玩家会频繁穿越场景,穿越场景时,玩家角色数据可能需要跨线程、 跨进程和物理机器。使用共享内存使玩家在跨物理机器时候,数据也不用保存到数据库并且再次从数据库加载,提高了效率(需要通过网络发送数据,但是省下了一 次写数据库和一次读数据库操作)。

    可以集中保存数据到数据库、集中控制保存的频率、也保证了世界的数据一致性。

    2. 共享内存并没节省游戏服务器内存

    可以看到,角色数据除了在共享内存中有一份外,HumanDB也有一份。HumanDB在Obj_Human::HeartBeat中把游戏更新的数据保存到共享内存。

    以前有个想法,认为既然内存数据被共享了,那么各个游戏服务器(场景)里面不需要再有一份数据,可以节省一些内存。因为共享内存也需要定时存盘,如果只有一份数据,游戏服务器和共享内存进程的互斥会降低游戏服务器效率。

    3. “共享内存”是否可以使用stl等模板库

    共享内存从机制上就是定长的数据块,当然自己可以在逻辑层加一些变长的处理,但是效率会打折扣,所以天龙的数据结构都是定长的结构。

    但是stl编程毕竟方便很多,增加开发效率也很重要。至于内存方面,可以自己接管stl的内存分配,加入高效的内存池。

    从本质上看,天龙的“内存共享”是一种数据缓存方法,那么使用stl数据结构也可以达到同样目的,当然只要效率能够保证即可。

  • 相关阅读:
    jackson 枚举 enum json 解析类型 返回数字 或者自定义文字 How To Serialize Enums as JSON Objects with Jackson
    Antd Pro V5 中ProTable 自定义查询参数和返回值
    ES6/Antd 代码阅读记录
    es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
    Antd Hooks
    使用.Net Core开发WPF App系列教程(其它 、保存控件内容为图片)
    使用.Net Core开发WPF App系列教程( 三、与.Net Framework的区别)
    使用.Net Core开发WPF App系列教程( 四、WPF中的XAML)
    使用.Net Core开发WPF App系列教程( 二、在Visual Studio 2019中创建.Net Core WPF工程)
    使用.Net Core开发WPF App系列教程( 一、.Net Core和WPF介绍)
  • 原文地址:https://www.cnblogs.com/dieangel/p/3326960.html
Copyright © 2011-2022 走看看