[转]天龙八部服务器端-共享内存的设计

摘要:
天龙八部的世界是ZoneBase。一个游戏服务器服务启动多个线程,每个线程服务多个场景。在WorldServer和GameServer进程中,池对象只获取共享内存对象并附加它。SMUPool模板类是一个特定的池对象。天龙八部实现了五种池类型,具体的数据类型对应五种结构:HumanSMU、GuildMU、MailSMU、PlayerShopSM和ItemSerialKeySMU。

一、服务器构架

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

[转]天龙八部服务器端-共享内存的设计第1张

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

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

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

enumSMPOOL_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

二、共享内存模块

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

[转]天龙八部服务器端-共享内存的设计第2张

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。

externSMUPool<GuildSMU>g_GuildSMUPool;

externSMUManager<GuildSMU>g_GuildSMUManager;

externSMUPool<MailSMU>g_MailSMUPool;

externSMUManager<MailSMU>g_MailSMUManager;

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

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

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

SMUPool<HumanSMU>g_HumanSMUPool;

SMUPool<PlayerShopSM>g_PlayerShopSMUPool;

SMUPool<ItemSerialKeySMU>g_ItemSerialSMUPool;

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

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

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

protected:

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

HumanDBm_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里面有类似定义:

structHumanSMU

{

SMUHeadm_SMUHead;

HUMAN_DB_ATTR_FLAGm_AttrFlag;//角色属性标志位

_HUMAN_DB_LOADm_HumanSM ;//角色基本信息

_BANK_DB_LOADm_BankSM ;//角色银行物品信息

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

_COOLDOWN_DB_LOAD_FOR_HUMANm_CooldownSM ;//角色身上的冷却信息

_XINFA_DB_LOADm_XinFaSM ;//角色学会的心法信息

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

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

_MISSION_DB_LOADm_MissionSM;//任务列表

_SETTING_DB_LOADm_SettingSM;//任务列表

_PET_DB_LIST_LOADm_PetListSM;//宠物列表

_BAG_DB_LOADm_BagSM;//角色背包物品信息

_EQUIP_DB_LOADm_EquipSM;//角色装备信息

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

_PRIVATE_INFO_DB_LOADm_PrivateInfoSM;//私人信息

};

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

HumanDB::HumanDB( )

{

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

HumanSMU*pSMU =g_HumanSMUPool.NewObj();

m_HumanSMU=pSMU;

m_AttrRegSM=newHUMAN_DB_ATTR_REG;

m_AttrReg=newHUMAN_DB_ATTR_REG;

m_AttrFlag=newHUMAN_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的消息响应如下:

UINTWGRetUserDataHandler::Execute(WGRetUserData*pPacket,Player*pPlayer )

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

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

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

_OBJ_HUMAN_INITinitHuman;

initHuman.m_pUseData= pData;

m_pHuman->Init( &initHuman,age ) ;

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

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

VOIDHumanDB::ValidateShareMem(BOOLbForceAll,BOOL&bUpdateAttr)

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

BOOLObj_Human::HeartBeat(UINTuTime )

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

七、总结

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

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

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

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

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

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

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

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

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

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

免责声明:文章转载自《[转]天龙八部服务器端-共享内存的设计》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ES之八:ES数据库重建索引——Reindex(数据迁移)ActiveMQ入门介绍下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

EF Code First 数据库迁移Migration剖析

1.简介 Entity Framework 的Code First 方式,提供了一种方式:编写模型Model,生成模型变更,根据模型变更修改数据库。 而其所以来的环境就是强大的Nuget,如果还在是VS2010一下的同学,请不要往下看了,将无一益处。 2.操作步骤 1)建立或修改Model,即实体类; 这里演示修改: public classBootStr...

easyui textarea回车导致datagrid 数据无法展示的问题

textarea换行 在easyui中的datagrid中使用行内编辑时textarea的换行保存到mysql数据库为 在textarea中输入回车符 在js读取textarea中的值有 然后到业务层转换到string中就有可能变成空格形式然后被存入数据库,当在取出此值的时候则会变成空格的形式,因此我们需要将不显示的 替换一下。  在js取textarea...

django 后台格式化数据库查询出的日期

在项目中,我遇到这样的情况,使用ajax获取查询出来的数据,而这些数据中某个字段是日期datetime格式,在模板中显示的样式很怪异。由于前端使用了js控件,也不能使用django的模板过滤器。 所以这种情况下,我想将日期从数据库中查询出来就使用固定好的格式。 django 中直接执行sql语句查询 from django.db import connec...

vim命令以及gcc编译器的常用cmd

Gcc常用命令:         -c    仅对源文件进行编译,不链接生成可执行文件。常用于查错和只生成目标文件。     -o    经过gcc处理过后的结果保存在-o后面的文件中,可以是多种文件。如无参数,默认名称不同     -v    查看版本信息     -g    在可执行文件中加入调试信息,相当于Windows下的Debug版本。方便使用g...

OB-连接Oceanbase

管理工具 OceanBase 客户端、MySQL 客户端、 OceanBase 开发者中心和 OceanBase 云平台 字符型管理工具 OceanBase 客户端 OceanBase 客户端(OBClient)同时兼容访问 OceanBase 数据库的 MySQL 以及 Oracle 租户 语法 obclient -u[用户名]@[租户名]#[集群名称]...

.NET 百万级 大数据插入、更新 ,支持多种数据库

功能介绍 (需要版本5.0.45) 大数据操作ORM性能瓶颈在实体转换上面,并且不能使用常规的Sql去实现 当列越多转换越慢,SqlSugar将转换性能做到极致,并且采用数据库最佳API 操作数据库达到极限性能 功能用法 BulkCopy性能远强于现有市场的 ORM框架,比 EFCore Efcore.Bulkextension快30% BulkUpdat...