邏輯層: 類World實現(xiàn)了wow的World,所有的邏輯處理
MaNGOS 下載,編譯,配置和運(yùn)行的基本步驟 下載和安裝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進(jìn)行構(gòu)造,構(gòu)造完成后,會得到mangosbinWin32_Debug文件夾
編譯scriptdev2我用的是VC9,打開mangoswinscriptdev2VC90.sln進(jìn)行構(gòu)造,構(gòu)造完成后,會得到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數(shù)據(jù)文件,文件命名規(guī)范為map_id(3位) tileY(2位) tileX(2位).map,如文件名為0002035.map,代表的是Azeroth(地圖id為000,tile坐標(biāo)為(35,20). 注:WOW客戶端的Tile對應(yīng)mangos中的grid,WOW客戶端的Chunk對應(yīng)mangos中的cell(1cell = 4 chunk)
按照常規(guī)流程(包括建立數(shù)據(jù)庫和配置服務(wù)器)把服務(wù)器跑起來,使用account create zzh1234567 zzh1234567 創(chuàng)建一個賬號,使用account set gmlevel zzh1234567 3設(shè)置為超級用戶
配置好客戶端后,運(yùn)行WOW,順利登陸,呵呵
通過安裝UDB來豐富場景FULL DB 9560 : HERE,參考這,在我安裝DB9560的時候,發(fā)現(xiàn)Mangos在LoadCreatureAddons的時候,加載creature template addons出錯,Mangos只要求creature template addons有7個字段,而creature template addons有9個字段我現(xiàn)在只是簡單地跳過LoadCreatureAddons的調(diào)用
運(yùn)行Mangos (運(yùn)行Mangos一節(jié)過期,是以前我針對國服3.1.3版本進(jìn)行的安裝配置) ,采用MaNGOS的工具ad從wow的MPQ中抽取map,得到的所有的map數(shù)據(jù)文件,文件命名規(guī)范為map_id(3位) tileY(2位) tileX(2位).map,如文件名為0002035.map,代表的是Azeroth(地圖id為000,tile坐標(biāo)為(35,20). 注:WOW客戶端的Tile對應(yīng)mangos中的grid,WOW客戶端的Chunk對應(yīng)mangos中的cell(1cell = 4 chunk)
配置好客戶端后,運(yùn)行WOW,順利登陸,呵呵
Mangos代碼閱讀
Mangos有13個工程
使用了4個外部工具庫,分別是: 跨平臺的網(wǎng)絡(luò)通訊框架The ADAPTIVE Communication Environment (ACE)
壓縮庫zlib
Socket通信庫 C++ Sockets Library (使用在realmd工程中,和使用在Mangosd工程中的RASocket,負(fù)責(zé)處理Remote Administration其他地方?jīng)]有使用到這個C++ Sockets Library )發(fā)現(xiàn)在C++ Sockets Library的TcpSocket::Open中存在一個問題,在n = connect(s, ad, ad);語句執(zhí)行后,如果n=-1,C++ Sockets Library會檢測是否ERR為WSAEWOULDBLOCK,否則表示成功,但在動態(tài)庫中使用TcpSocket的時候,我發(fā)現(xiàn)n = connect(s, ad, ad);語句執(zhí)行后,n=-1,ERR會為0,這個時候連接也是成功了,但TcpSocket::Open會當(dāng)做不成功處理我發(fā)現(xiàn)這個問題,但沒有時間去探究原因,也許并不是一個問題
C++的并行編程模板庫Threading Building Blocks (tbb 和 tbbmalloc)
Mangos的實現(xiàn)分為:登錄服務(wù)器(realmd)和世界服務(wù)器(mangosd+game)realmd和mangos共用了Mangos公共庫(shared)
工程shared
提供了通用功能,包括了數(shù)據(jù)庫的封裝類,實現(xiàn)了對MySql的訪問,同樣,我們可以編寫派生類來支持其他的數(shù)據(jù)庫
工程script
提供了腳本接口,并實現(xiàn)了簡單的幾個腳本,封裝為DLL,提供給game使用,具體可參考:MaNGOS腳本接口
通過使用不同的腳本DLL來替換share的這個DLL,可以讓game具有更強(qiáng)的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是世界服務(wù)器的管理器,負(fù)責(zé)初始化工作和啟動世界服務(wù)器各層的線程,這些工作主要是由類Master來實現(xiàn)具體是: 使用三個數(shù)據(jù)庫對象WorldDatabase和CharacterDatabase和loginDatabase,初始化三大數(shù)據(jù)庫:World Database和Character Database和login Database,并為每個數(shù)據(jù)庫的訪問都啟動一個DB delay threads具體的數(shù)據(jù)庫操作功能都是由Mangos公共庫shared來提供
調(diào)用sWorld.SetInitialWorldSettings,對World進(jìn)行初始化,包括加載所有的游戲數(shù)據(jù)和初始化各種更新定時器和郵件定時器,還有些其他的初始化工作類World的成員函數(shù)SetInitialWorldSettings調(diào)用成員函數(shù)LoadConfigSettings解析mangosd.conf,解析后內(nèi)容放入uint32 m_configs[CONFIG_VALUE_COUNT]中
加載的游戲數(shù)據(jù)有:
DBC數(shù)據(jù)
Objects數(shù)據(jù)
Spells數(shù)據(jù)
Pooling數(shù)據(jù)
Game Event數(shù)據(jù)
loot數(shù)據(jù)
技能數(shù)據(jù)
所有其他的游戲數(shù)據(jù),包括Waypoints和Trainers等等等
腳本數(shù)據(jù)
其他的初始化工作有:
初始化MapManager,啟動Map System
初始化Battlegrounds,啟動BattleGround System
初始化DailyQuestResetTime
初始化sGameEventMgr,Starting Game Event system
類Master啟動WorldRunnable,開始游戲邏輯Heartbeat for the World,由Master創(chuàng)建,并設(shè)置線程為最高優(yōu)先級
類Master啟動CliRunnable:Command Line Interface handling thread,由Master創(chuàng)建CliRunnable運(yùn)行時候會生成一個WorldDatabase線程,在接收到輸入后會調(diào)用sWorld.QueueCliCommand把Cmd放入到World::cliCmdQueue中
mangosd的線程總共有(1+3+1+1+1+2 +1 =10)10個線程 主線程Master
2個網(wǎng)絡(luò)線程ReactorRunnable(可配置數(shù)目)(網(wǎng)絡(luò)層)
一個World線程(邏輯層)
三個DB線程(數(shù)據(jù)層)
一個CLI線程(輸入層),運(yùn)行時候會生成一個WorldDatabase線程
一個RA線程(管理層)
一個freeze catcher 線程(可選)
工程g3dlite:游戲邏輯層的底層庫
工程framework:系統(tǒng)框架
工程realm
負(fù)責(zé)登陸和選擇游戲服務(wù)器,進(jìn)行負(fù)載均衡用到了C++ Sockets Library進(jìn)行登錄處理,采用select I/O模型實現(xiàn)了Wow, Mangos登錄時的SRP6認(rèn)證客戶端作為它的client連接到realm server認(rèn)證和選擇了mangos server就斷開 而mangos server和realm server則不進(jìn)行連接,只是通過數(shù)據(jù)庫交互數(shù)據(jù):mangos server把自己的狀態(tài)和擁有的角色數(shù)放入庫中realm server會讀取數(shù)據(jù)庫中的這些信息來獲知mangos server的狀態(tài) 數(shù)據(jù)庫realm的realmlist表保存了realm的列表
realm通過如下事件處理函數(shù)來負(fù)責(zé)登陸和選擇游戲服務(wù)器
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進(jìn)行身份認(rèn)證,并選擇登錄上哪個mangos serveruser登錄到mangos server后,將不再和realm server交互
參考: Wow 服務(wù)器解析
工程game
game:是Mangos的核心代碼,網(wǎng)絡(luò)層和邏輯層代碼(采用了ACE反應(yīng)器(Reactor)模式)
網(wǎng)絡(luò)層: WorldSocket :負(fù)責(zé)網(wǎng)絡(luò)IO,而類WorldSession負(fù)責(zé)邏輯處理WorldSocket和WorldSession分別在獨立的線程ReactorRunnable和WorldRunnable中運(yùn)行,使用WorldSession中的消息隊列_recvQueue來進(jìn)行數(shù)據(jù)緩沖在WorldSocket接收到網(wǎng)咯輸入后,會調(diào)用m_Session->QueuePacket (new_pct);把網(wǎng)絡(luò)包放入WorldSession的_recvQueue所以,可以看到WorldSocket 是Mangos game的網(wǎng)絡(luò)層,而WorldSession是邏輯處理層WorldSocketMgr是網(wǎng)絡(luò)層的一個管理器,它負(fù)責(zé)指派WorldSocket歸哪個ReactorRunnable管理(Mangos可創(chuàng)建多個ReactorRunnable,缺省是2個)
WorldSocketMgr(Manages all sockets connected to peers and network threads)管理所有的連接WorldSocketWorldSocketMgr的WorldSocketMgr::StartNetwork對8085(缺省)端口進(jìn)行偵聽邏輯處理的循環(huán)是在World::Update中循環(huán)處理包括:
刷新更新定時器
刷新游戲定時器和處理游戲關(guān)閉
處理日常任務(wù)
處理拍賣
刷新SessionsWorld::UpdateSessions會調(diào)用所有WorldSession的WorldSession::Update在WorldSession::Update中進(jìn)行邏輯處理
處理天氣
刷新uptime table
刷新Objects,包括maps,transport,creatures,,,,
刷新所有running battlegrounds
刷新SqlResultQueue, 邏輯層和數(shù)據(jù)層是通過Queue來進(jìn)行異步操作的(用了AsyncPQuery和SqlResultQueue)
處理尸體移除
處理游戲事件
處理 Move all creatures with "delayed move" and remove and delete all objects with "delayed remove"
處理InstanceSaveManager的刷新
調(diào)用World::ProcessCliCommands,處理CLI從cliCmdQueue取得cmd進(jìn)行解析執(zhí)行所有有效的Cmd,都可以在ChatHandler::getCommandTable中找到
類WorldSession: 類WorldSession負(fù)責(zé)邏輯處理
void WorldSession::SendPacket(WorldPacket const* packet) 負(fù)責(zé)發(fā)包給客戶端,直接發(fā)包,沒有輸出緩沖隊列
在WorldSession::Update中進(jìn)行邏輯處理World::UpdateSessions會調(diào)用所有WorldSession的WorldSession::Update
執(zhí)行語句OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];得到opHandle
根據(jù)得到的opHandle,執(zhí)行(this->*opHandle.handler)(*packet);
WorldSession::HandlePlayerLogin處理玩家登陸游戲
構(gòu)建Player
Player::LoadFromDB從數(shù)據(jù)庫中加載玩家數(shù)據(jù)在Player::LoadFromDB中會調(diào)用SetMap(MapManager::Instance().CreateMap(GetMapId() , this));加載當(dāng)前player所在的map
Player::SetPosition在Player運(yùn)動的時候,改變位置,保存處理夸區(qū)
類Map實現(xiàn)了一個state machine,采用state pattern組織了Gid的4個state object:InvalidState;ActiveState;IdleState;RemovalState
game中的管理器有: ObjectMgr
mMangosStringLocaleMap 關(guān)聯(lián)到mangos_string table
m_scriptNames 關(guān)聯(lián)到tables: creature_template;gameobject_template;item_templat e;areatrigger_scripts;instance_template
mCreatureLocaleMap 關(guān)聯(lián)到locales_creature table
mGameObjectLocaleMap關(guān)聯(lián)到locales_gameobject table
mItemLocaleMap關(guān)聯(lián)到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進(jìn)行管理ObjectMgr以GUID方式,管理了characters,creature,item_instance,gameobject,auctionhouse,mail,item_text,corpse,arena_team,character_equipmentsets





Player狀態(tài)
/// 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)
};
生物狀態(tài)
enum DeathState
{
ALIVE = 0,
JUST_DIED = 1,
CORPSE = 2,
DEAD = 3,
JUST_ALIVED = 4,
DEAD_FALLING= 5
};
玩家登陸
服務(wù)器端在連接打開后,會發(fā)SMSG_AUTH_CHALLENGE到客戶端客戶端從服務(wù)器端發(fā)送回來的種子和 SRP6 數(shù)據(jù)中產(chǎn)生隨機(jī)種子,生成 SHA1 字符串,用這些數(shù)據(jù)生成 CMSG_AUITH_SESSION 數(shù)據(jù)包,發(fā)送給服務(wù)端這個過程是沒有經(jīng)過加密的

客戶端發(fā)送SMSG_AUTH_SESSION到服務(wù)器
服務(wù)器處理SMSG_AUTH_SESSION
服務(wù)器發(fā)送SMSG_AUTH_RESPONSE給客戶端
服務(wù)器發(fā)送SMSG_ADDON_INFO給客戶端
服務(wù)器發(fā)送SMSG_CLIENTCACHE_VERSION給客戶端
服務(wù)器發(fā)送SMSG_TUTORIAL_FLAGS給客戶端
packet結(jié)構(gòu) SMSG_AUTH_SESSION 是client packet有一個頭部(ClientPktHeader),后面是數(shù)據(jù)塊
SMSG_AUTH_RESPONSE 是server packet有一個頭部(ServicePktHeader),后面是數(shù)據(jù)塊
SMSG_AUTH_RESPONSE 的包組織
SMSG_AUTH_RESPONSE 的opcode是01EE,ByteBuffer大小為1 + 4 + 1 + 4 + 1=11一個SMSG_AUTH_RESPONSE 的數(shù)據(jù)如下: 在構(gòu)造了SMSG_AUTH_RESPONSE packet后,WorldSocket::SendPacket會根據(jù)SMSG_AUTH_RESPONSE packet構(gòu)造出一個ServerPktHeader,并對ServerPktHeader中的數(shù)據(jù)header進(jìn)行加密發(fā)送加密采用m_Crypt.EncryptSend ((uint8*)header.header, header.getHeaderLength()); WorldSocket::handle_input_header會對從客戶端接收來的數(shù)據(jù)進(jìn)行解密,解密采用m_Crypt.DecryptRecv ((uint8*) m_Header.rd_ptr (), sizeof (ClientPktHeader));
角色枚舉 玩家登上服務(wù)器后,從客戶端發(fā)送SMSG_CHAR_ENUM到服務(wù)器
在服務(wù)器端
根據(jù){ "CMSG_CHAR_ENUM", STATUS_AUTHED, &WorldSession::HandleCharEnumOpcode },服務(wù)器會調(diào)用WorldSession::HandleCharEnumOpcode
WorldSession::HandleCharEnumOpcode會調(diào)用CharacterDatabase.AsyncPQuery,進(jìn)行異步查詢后,調(diào)用CharacterHandler::HandleCharEnumCallback
CharacterHandler::HandleCharEnumCallback會回頭調(diào)用session->HandleCharEnum(result);
WorldSession::HandleCharEnum根據(jù)characters數(shù)據(jù)庫的查詢結(jié)果,調(diào)用Player::BuildEnumData,加載角色數(shù)據(jù),構(gòu)造SMSG_CHAR_ENUM返回包,然后發(fā)送回客戶端

角色創(chuàng)建
角色的初始化裝備在CharStartOutfit.dbc
角色的創(chuàng)建和選擇的設(shè)置都在 ChrRaces.dbc
角色的創(chuàng)建屬性都在Playercreateinfo,包括出生地和出生屬性
playercreateinfo_item表是創(chuàng)建一個新人物時,人物默認(rèn)帶的所有Item的表
playercreateinfo_spell表是創(chuàng)建一個新人物時,人物默認(rèn)帶的所有Spell的表
角色刪除
