转自:陈波的博客
原文:http://blog.163.com/cp7618@yeah/blog/static/702347772010111434342665/
交叉阅读:
MaNGOS之验证Realm登录服务器
Mangos之SMSG_COMPRESSED_UPDATE_O…
Mangos之异步数据库查询
Mangos服务器会话WorldSession
Mangos之Socket处理方式
reamld中socket的处理方式
Mangosd消息的对称加密解析
详解验证Mangos服务器:消息SMSG_A…
Mangos游戏世界主线程解析
逻辑层: 类World实现了wow的World,所有的逻辑处理
MaNGOS 下载,编译,配置和运行的基本步骤 下载和安装msysgit,用于代码管理我使用的是Git-1.6.5.1-preview20091022.exe
下载和安装tortoisegit,用于代码管理我使用的是TortoiseGit-1.3.2.0-32bit.msi
使用git://github.com/mangos/mangos.git,从github提取mangos代码
采用Git GUI工具的Clone Existing Repository,得到 mangos代码(我的是9560,安装UDB参考这)
使用git://github.com/scriptdev2/scriptdev2.git,从github提取scriptdev2代码
采用Git GUI工具的Clone Existing Repository,得到 scriptdev2代码.
编译mangos我用的是VC9,打开mangoswinmangosdVC90.sln进行构造,构造完成后,会得到mangosbinWin32_Debug文件夹
编译scriptdev2我用的是VC9,打开mangoswinscriptdev2VC90.sln进行构造,构造完成后,会得到scriptdev2binWin32_Debug文件夹也可参考
pandore
拷贝mangossrcmangosd目录下的mangosd.conf.dist.in为mangosbinWin32_Debugmangosd.conf
拷贝mangossrcrealmd目录下的realmd.conf.dist.in为mangosbinWin32_Debugrealmd.conf
从http://www.wowtaiwan.com.tw/下载和安装台服WOW,并升级到最新版本我使用的是台服WOW 3.3.2 build 11403
,采用MaNGOS的工具ad从wow的MPQ中抽取map,得到的所有的map数据文件,文件命名规范为map_id(3位) tileY(2位) tileX(2位).map,如文件名为0002035.map,代表的是Azeroth(地图id为000,tile坐标为(35,20). 注:WOW客户端的Tile对应mangos中的grid,WOW客户端的Chunk对应mangos中的cell(1cell = 4 chunk)
按照常规流程(包括建立数据库和配置服务器)把服务器跑起来,使用account create zzh1234567 zzh1234567 创建一个账号,使用account set gmlevel zzh1234567 3设置为超级用户
配置好客户端后,运行WOW,顺利登陆,呵呵
通过安装UDB来丰富场景FULL DB 9560 : HERE,参考这,在我安装DB9560的时候,发现Mangos在LoadCreatureAddons的时候,加载creature template addons出错,Mangos只要求creature template addons有7个字段,而creature template addons有9个字段我现在只是简单地跳过LoadCreatureAddons的调用
运行Mangos (运行Mangos一节过期,是以前我针对国服3.1.3版本进行的安装配置) ,采用MaNGOS的工具ad从wow的MPQ中抽取map,得到的所有的map数据文件,文件命名规范为map_id(3位) tileY(2位) tileX(2位).map,如文件名为0002035.map,代表的是Azeroth(地图id为000,tile坐标为(35,20). 注:WOW客户端的Tile对应mangos中的grid,WOW客户端的Chunk对应mangos中的cell(1cell = 4 chunk)
配置好客户端后,运行WOW,顺利登陆,呵呵
Mangos代码阅读
Mangos有13个工程
使用了4个外部工具库,分别是: 跨平台的网络通讯框架The ADAPTIVE Communication Environment (ACE)
压缩库zlib
Socket通信库 C++ Sockets Library (使用在realmd工程中,和使用在Mangosd工程中的RASocket,负责处理Remote Administration其他地方没有使用到这个C++ Sockets Library )发现在C++ Sockets Library的TcpSocket::Open中存在一个问题,在n = connect(s, ad, ad);语句执行后,如果n=-1,C++ Sockets Library会检测是否ERR为WSAEWOULDBLOCK,否则表示成功,但在动态库中使用TcpSocket的时候,我发现n = connect(s, ad, ad);语句执行后,n=-1,ERR会为0,这个时候连接也是成功了,但TcpSocket::Open会当做不成功处理我发现这个问题,但没有时间去探究原因,也许并不是一个问题
C++的并行编程模板库Threading Building Blocks (tbb 和 tbbmalloc)
Mangos的实现分为:登录服务器(realmd)和世界服务器(mangosd+game)realmd和mangos共用了Mangos公共库(shared)
工程shared
提供了通用功能,包括了数据库的封装类,实现了对MySql的访问,同样,我们可以编写派生类来支持其他的数据库
工程script
提供了脚本接口,并实现了简单的几个脚本,封装为DLL,提供给game使用,具体可参考:MaNGOS脚本接口
通过使用不同的脚本DLL来替换share的这个DLL,可以让game具有更强的AIScriptDev2 就是一个这样的库ScriptDev2 is a replacement for the Script Library that comes with MaNGOS( http://www.getmangos.com ) written in C++ and is compatible with Windows and Linux. It provides scripts for NPCs, Boss events, and Items currently. Once ScriptDev2 is compiled it is automatically run by MaNGOS on server startup.
工程mangosd
mangos是世界服务器的管理器,负责初始化工作和启动世界服务器各层的线程,这些工作主要是由类Master来实现具体是: 使用三个数据库对象WorldDatabase和CharacterDatabase和loginDatabase,初始化三大数据库:World Database和Character Database和login Database,并为每个数据库的访问都启动一个DB delay threads具体的数据库操作功能都是由Mangos公共库shared来提供
调用sWorld.SetInitialWorldSettings,对World进行初始化,包括加载所有的游戏数据和初始化各种更新定时器和邮件定时器,还有些其他的初始化工作类World的成员函数SetInitialWorldSettings调用成员函数LoadConfigSettings解析mangosd.conf,解析后内容放入uint32 m_configs[CONFIG_VALUE_COUNT]中
加载的游戏数据有:
DBC数据
Objects数据
Spells数据
Pooling数据
Game Event数据
loot数据
技能数据
所有其他的游戏数据,包括Waypoints和Trainers等等等
脚本数据
其他的初始化工作有:
初始化MapManager,启动Map System
初始化Battlegrounds,启动BattleGround System
初始化DailyQuestResetTime
初始化sGameEventMgr,Starting Game Event system
类Master启动WorldRunnable,开始游戏逻辑Heartbeat for the World,由Master创建,并设置线程为最高优先级
类Master启动CliRunnable:Command Line Interface handling thread,由Master创建CliRunnable运行时候会生成一个WorldDatabase线程,在接收到输入后会调用sWorld.QueueCliCommand把Cmd放入到World::cliCmdQueue中
mangosd的线程总共有(1+3+1+1+1+2 +1 =10)10个线程 主线程Master
2个网络线程ReactorRunnable(可配置数目)(网络层)
一个World线程(逻辑层)
三个DB线程(数据层)
一个CLI线程(输入层),运行时候会生成一个WorldDatabase线程
一个RA线程(管理层)
一个freeze catcher 线程(可选)
工程g3dlite:游戏逻辑层的底层库
工程framework:系统框架
工程realm
负责登陆和选择游戏服务器,进行负载均衡用到了C++ Sockets Library进行登录处理,采用select I/O模型实现了Wow, Mangos登录时的SRP6认证客户端作为它的client连接到realm server认证和选择了mangos server就断开 而mangos server和realm server则不进行连接,只是通过数据库交互数据:mangos server把自己的状态和拥有的角色数放入库中realm server会读取数据库中的这些信息来获知mangos server的状态 数据库realm的realmlist表保存了realm的列表
realm通过如下事件处理函数来负责登陆和选择游戏服务器
const AuthHandler table[] =
{
{ AUTH_LOGON_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleLogonChallenge },
{ AUTH_LOGON_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleLogonProof },
{ AUTH_RECONNECT_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleReconnectChallenge},
{ AUTH_RECONNECT_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleReconnectProof },
{ REALM_LIST, STATUS_AUTHED, &AuthSocket::_HandleRealmList },
{ XFER_ACCEPT, STATUS_CONNECTED, &AuthSocket::_HandleXferAccept },
{ XFER_RESUME, STATUS_CONNECTED, &AuthSocket::_HandleXferResume },
{ XFER_CANCEL, STATUS_CONNECTED, &AuthSocket::_HandleXferCancel }
}; 登录处理:
user登录到realm server进行身份认证,并选择登录上哪个mangos serveruser登录到mangos server后,将不再和realm server交互
参考: Wow 服务器解析
工程game
game:是Mangos的核心代码,网络层和逻辑层代码(采用了ACE反应器(Reactor)模式)
网络层: WorldSocket :负责网络IO,而类WorldSession负责逻辑处理WorldSocket和WorldSession分别在独立的线程ReactorRunnable和WorldRunnable中运行,使用WorldSession中的消息队列_recvQueue来进行数据缓冲在WorldSocket接收到网咯输入后,会调用m_Session->QueuePacket (new_pct);把网络包放入WorldSession的_recvQueue所以,可以看到WorldSocket 是Mangos game的网络层,而WorldSession是逻辑处理层WorldSocketMgr是网络层的一个管理器,它负责指派WorldSocket归哪个ReactorRunnable管理(Mangos可创建多个ReactorRunnable,缺省是2个)
WorldSocketMgr(Manages all sockets connected to peers and network threads)管理所有的连接WorldSocketWorldSocketMgr的WorldSocketMgr::StartNetwork对8085(缺省)端口进行侦听逻辑处理的循环是在World::Update中循环处理包括:
刷新更新定时器
刷新游戏定时器和处理游戏关闭
处理日常任务
处理拍卖
刷新SessionsWorld::UpdateSessions会调用所有WorldSession的WorldSession::Update在WorldSession::Update中进行逻辑处理
处理天气
刷新uptime table
刷新Objects,包括maps,transport,creatures,,,,
刷新所有running battlegrounds
刷新SqlResultQueue, 逻辑层和数据层是通过Queue来进行异步操作的(用了AsyncPQuery和SqlResultQueue)
处理尸体移除
处理游戏事件
处理 Move all creatures with "delayed move" and remove and delete all objects with "delayed remove"
处理InstanceSaveManager的刷新
调用World::ProcessCliCommands,处理CLI从cliCmdQueue取得cmd进行解析执行所有有效的Cmd,都可以在ChatHandler::getCommandTable中找到
类WorldSession: 类WorldSession负责逻辑处理
void WorldSession::SendPacket(WorldPacket const* packet) 负责发包给客户端,直接发包,没有输出缓冲队列
在WorldSession::Update中进行逻辑处理World::UpdateSessions会调用所有WorldSession的WorldSession::Update
执行语句OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];得到opHandle
根据得到的opHandle,执行(this->*opHandle.handler)(*packet);
WorldSession::HandlePlayerLogin处理玩家登陆游戏
构建Player
Player::LoadFromDB从数据库中加载玩家数据在Player::LoadFromDB中会调用SetMap(MapManager::Instance().CreateMap(GetMapId() , this));加载当前player所在的map
Player::SetPosition在Player运动的时候,改变位置,保存处理夸区
类Map实现了一个state machine,采用state pattern组织了Gid的4个state object:InvalidState;ActiveState;IdleState;RemovalState
game中的管理器有: ObjectMgr
mMangosStringLocaleMap 关联到mangos_string table
m_scriptNames 关联到tables: creature_template;gameobject_template;item_templat e;areatrigger_scripts;instance_template
mCreatureLocaleMap 关联到locales_creature table
mGameObjectLocaleMap关联到locales_gameobject table
mItemLocaleMap关联到locales_item table
mQuestLocaleMap –> locales_quest
mNpcTextLocaleMap –> locales_npc_text
mPageTextLocaleMap –> locales_page_text
mGossipMenuItemsLocaleMap –> locales_gossip_menu_option
mPointOfInterestLocaleMap –> locales_points_of_interest
…
对象类层次
对象的类层次如下,所有的Object都由ObjectMgr进行管理ObjectMgr以GUID方式,管理了characters,creature,item_instance,gameobject,auctionhouse,mail,item_text,corpse,arena_team,character_equipmentsets
Player状态
/// Player state
enum SessionStatus
{
STATUS_AUTHED = 0, ///< Player authenticated (_player==NULL, m_playerRecentlyLogout = false or will be reset before handler call, m_GUID have garbage)
STATUS_LOGGEDIN, ///< Player in game (_player!=NULL, m_GUID == _player->GetGUID(), inWorld())
STATUS_TRANSFER, ///< Player transferring to another map (_player!=NULL, m_GUID == _player->GetGUID(), !inWorld())
STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, ///< _player!= NULL or _player==NULL && m_playerRecentlyLogout, m_GUID store last _player guid)
STATUS_NEVER ///< Opcode not accepted from client (deprecated or server side only)
};
生物状态
enum DeathState
{
ALIVE = 0,
JUST_DIED = 1,
CORPSE = 2,
DEAD = 3,
JUST_ALIVED = 4,
DEAD_FALLING= 5
};
玩家登陆
服务器端在连接打开后,会发SMSG_AUTH_CHALLENGE到客户端客户端从服务器端发送回来的种子和 SRP6 数据中产生随机种子,生成 SHA1 字符串,用这些数据生成 CMSG_AUITH_SESSION 数据包,发送给服务端这个过程是没有经过加密的
客户端发送SMSG_AUTH_SESSION到服务器
服务器处理SMSG_AUTH_SESSION
服务器发送SMSG_AUTH_RESPONSE给客户端
服务器发送SMSG_ADDON_INFO给客户端
服务器发送SMSG_CLIENTCACHE_VERSION给客户端
服务器发送SMSG_TUTORIAL_FLAGS给客户端
packet结构 SMSG_AUTH_SESSION 是client packet有一个头部(ClientPktHeader),后面是数据块
SMSG_AUTH_RESPONSE 是server packet有一个头部(ServicePktHeader),后面是数据块
SMSG_AUTH_RESPONSE 的包组织
SMSG_AUTH_RESPONSE 的opcode是01EE,ByteBuffer大小为1 + 4 + 1 + 4 + 1=11一个SMSG_AUTH_RESPONSE 的数据如下: 在构造了SMSG_AUTH_RESPONSE packet后,WorldSocket::SendPacket会根据SMSG_AUTH_RESPONSE packet构造出一个ServerPktHeader,并对ServerPktHeader中的数据header进行加密发送加密采用m_Crypt.EncryptSend ((uint8*)header.header, header.getHeaderLength()); WorldSocket::handle_input_header会对从客户端接收来的数据进行解密,解密采用m_Crypt.DecryptRecv ((uint8*) m_Header.rd_ptr (), sizeof (ClientPktHeader));
角色枚举 玩家登上服务器后,从客户端发送SMSG_CHAR_ENUM到服务器
在服务器端
根据{ "CMSG_CHAR_ENUM", STATUS_AUTHED, &WorldSession::HandleCharEnumOpcode },服务器会调用WorldSession::HandleCharEnumOpcode
WorldSession::HandleCharEnumOpcode会调用CharacterDatabase.AsyncPQuery,进行异步查询后,调用CharacterHandler::HandleCharEnumCallback
CharacterHandler::HandleCharEnumCallback会回头调用session->HandleCharEnum(result);
WorldSession::HandleCharEnum根据characters数据库的查询结果,调用Player::BuildEnumData,加载角色数据,构造SMSG_CHAR_ENUM返回包,然后发送回客户端
角色创建
角色的初始化装备在CharStartOutfit.dbc
角色的创建和选择的设置都在 ChrRaces.dbc
角色的创建属性都在Playercreateinfo,包括出生地和出生属性
playercreateinfo_item表是创建一个新人物时,人物默认带的所有Item的表
playercreateinfo_spell表是创建一个新人物时,人物默认带的所有Spell的表
角色删除