日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Mangos魔兽世界服务端初探(1)--游戏服务端主体结构与消息分发

發布時間:2023/12/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mangos魔兽世界服务端初探(1)--游戏服务端主体结构与消息分发 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

魔獸時間是暴雪著名的網絡游戲,我以前也玩過一段時間的戰士,這款游戲目前已進入晚年時期,不過里面各種豐富的游戲系統和游戲內容都非常讓人印象深刻。開源的Mangos項目模擬魔獸服務器端非常成功,目前國內外也有不少基于Mangos模擬器而搭建的私服,多數服務端運轉良好,非常穩定。國外有一個叫做MonsterWOW的魔獸私服,單服承載5000人,總共有幾組服務器,幾萬人同時在線,這是我在網站上親眼看到的實時數據,一般來講,如果對MMORPG游戲服務端稍微熟悉都知道,5000人同服在線,而且允許游戲邏輯的是一臺單獨的服務器,支撐這么龐大一個游戲世界,肯定有非常過人之處,至少據我所知國內的單服性能與之相比都有較大差距,國內分布式的服務端架構基本也是將游戲邏輯分散到多臺服務器上,單一世界承載數量也不算很高。幾年前的Eve Online單一世界可以承載兩萬多玩家同時在線、實時交互。我想國內多數MMORPG服務端的承載人數應該都是在七八百、一兩千這個數量級的。Mangos的源代碼下載下來好久了,一直沒時間研究,它目前是C++寫成的,我的主要方向是C#,不過我一直有將C#做游戲服務端的打算,所以既然它有那么多過人之處,就算不能掌握全部也應該研究學習一下。

???? 今天粗略地看了一下,服務端主要又三大塊組成,數據庫、服務端邏輯、腳本。數據庫用的MySQL,這里不是很關鍵暫且不說,腳本有自己的腳步引擎,簡單的任務、戰斗等都可以通過數據庫配置相應條目來完成,復雜的戰斗AI等在腳步庫中由C++直接寫成,這個腳本庫是要被編譯為機器代碼的,執行效率相當高效,例如巫妖王的戰斗比較復雜就用C++寫,其它簡單的就配置在數據庫中由腳步引擎來驅動執行。國內不少服務端都是非常老式的C++早期服務端結構,不少嵌入了lua解釋器,大量的寫lua腳本,甚至用lua寫邏輯。我個人很不理解這種方式,你說效率高吧,lua再快能多塊,解釋執行和編譯執行不是一個數量級的,看看服務端的承載人數就知道了,lua JIT即時編譯都不靠譜。或許有人會說lua簡單,策劃都可以學習之后寫腳本,事實上卻是寫腳本的人寫出一大堆的不敢說垃圾代碼,也算是低質量代碼,這樣更加拖累服務端的性能了。為何不學學一些比較優秀的項目,也來想辦法搞一個腳本引擎,然后寫出工具就可以讓策劃配置大量的任務、戰斗這些游戲內容,復雜的邏輯直接由游戲程序員來編寫,用C++、C#多好,搞不懂為什么lua已經成為好多公司的標準了,就算不是lua也是python。就說劍網3這個游戲吧,我玩了兩年多的劍純陽,對這款游戲的體驗有足夠的了解。我們不和其它游戲的游戲比,至少在國內算優秀作品,也取得了一定的成功,雖然說抄魔獸也有點多。以前玩游戲的時候,二十多個人進個副本放些技能卡得要命,人多了在一個地圖直接卡到爆,后來一個好朋友和我說,劍網3服務端用lua寫了好多東西,能lua的多半都用lua了,一個天子峰老6,這個Boss的lua腳本竟有好幾個lua文件,每個文件幾百行代碼,我想啊,服務端完全充斥著這種低質量的腳本,還談什么效率,談什么承載人數,能跑起來就不錯了。關鍵是那個Boss的戰斗并不復雜,和魔獸很多Boss比起來就算是非常簡單的Boss了,mangos服務端一個復雜Boss的代碼都比這個簡單很多,代碼總數也僅兩百多行,執行效率更不是一個數量級的。這里發發牢騷,不用較真,言歸正傳。

???? Mangos服務端是一個多線程、邏輯單線程的服務端。每個線程內部都采用循環結構,主線程啟動后將創建多個工作線程,主要包括負責游戲世界運作的核心線程,具有處理用戶請求,執行定時器的能力。其它幾個工作線程還有網絡Io,該線程啟動后其內部將使用線程池進行網絡Io操作,不間斷地接收數據包,并存儲到相關玩家的消息隊列中,由世界線程進行處理,其它幾個工作線程先不討論,因為今天也是第一次看mangos的源代碼.務端啟動后這些線程將永不停息地工作。世界線程是服務器的核心,負責處理所有玩家操作請求,定時器、AI等。以下是世界線程啟動后執行的代碼:

?///?Heartbeat?for?the?World void?WorldRunnable::run() {///-?Init?new?SQL?thread?for?the?world?databaseWorldDatabase.ThreadStart();????????????????????????????//?let?thread?do?safe?mySQL?requests?(one?connection?call?enough)sWorld.InitResultQueue();uint32?realCurrTime?=?0;uint32?realPrevTime?=?WorldTimer::tick();uint32?prevSleepTime?=?0;???????????????????????????????//?used?for?balanced?full?tick?time?length?near?WORLD_SLEEP_CONST///-?While?we?have?not?World::m_stopEvent,?update?the?worldwhile?(!World::IsStopped()){++World::m_worldLoopCounter;realCurrTime?=?WorldTimer::getMSTime();uint32?diff?=?WorldTimer::tick();sWorld.Update(diff);realPrevTime?=?realCurrTime;//?diff?(D0)?include?time?of?previous?sleep?(d0)?+?tick?time?(t0)//?we?want?that?next?d1?+?t1?==?WORLD_SLEEP_CONST//?we?can't?know?next?t1?and?then?can?use?(t0?+?d1)?==?WORLD_SLEEP_CONST?requirement//?d1?=?WORLD_SLEEP_CONST?-?t0?=?WORLD_SLEEP_CONST?-?(D0?-?d0)?=?WORLD_SLEEP_CONST?+?d0?-?D0if?(diff?<=?WORLD_SLEEP_CONST?+?prevSleepTime){prevSleepTime?=?WORLD_SLEEP_CONST?+?prevSleepTime?-?diff;ACE_Based::Thread::Sleep(prevSleepTime);}elseprevSleepTime?=?0; #ifdef?WIN32if?(m_ServiceStatus?==?0)?World::StopNow(SHUTDOWN_EXIT_CODE);while?(m_ServiceStatus?==?2)?Sleep(1000); #endif}sWorld.CleanupsBeforeStop();sWorldSocketMgr->StopNetwork();MapManager::Instance().UnloadAll();?????????????????????//?unload?all?grids?(including?locked?in?memory)///-?End?the?database?threadWorldDatabase.ThreadEnd();??????????????????????????????//?free?mySQL?thread?resources }

因為是直接粘貼的,看上去比較亂,這里先作一下說明,這是世界線程的根循環結構,在while(!World::IsStopped())內部只有一個核心函數調用,其他都是一些控制更新時間之類的代碼,不用太關注:

sWorld.Update(diff);

sWorld是單一實例的World對象,它代表了整個游戲世界,和多數MMORPG一樣,啟動后進入根循環,在運行內部一直調用更新整個游戲世界的Update函數,服務端不停的Update游戲世界,每次Update能在100毫秒內完成,則客戶端會感到非常流暢。在根循環退出后,清理服務器相關資源,線程結束被回收。Mangos使用的是開源跨平臺的網絡、線程處理庫ACE,這個東西粗略的看了一下,比較復雜,如果要研究透徹是很困難的事,這里提一下,不對ACE探討。到這里我們僅僅需要關注一個函數了,就是World的Update方法內部到底在干什么?

void?World::Update(uint32?diff) {///-?Update?the?different?timersfor?(int?i?=?0;?i?<?WUPDATE_COUNT;?++i){if?(m_timers[i].GetCurrent()?>=?0)m_timers[i].Update(diff);elsem_timers[i].SetCurrent(0);}///-?Update?the?game?time?and?check?for?shutdown?time_UpdateGameTime();///-Update?mass?mailer?tasks?if?anysMassMailMgr.Update();///?Handle?daily?quests?reset?timeif?(m_gameTime?>?m_NextDailyQuestReset)ResetDailyQuests();///?Handle?weekly?quests?reset?timeif?(m_gameTime?>?m_NextWeeklyQuestReset)ResetWeeklyQuests();///?Handle?monthly?quests?reset?timeif?(m_gameTime?>?m_NextMonthlyQuestReset)ResetMonthlyQuests();///?Handle?monthly?quests?reset?timeif?(m_gameTime?>?m_NextCurrencyReset)ResetCurrencyWeekCounts();///?<ul><li>?Handle?auctions?when?the?timer?has?passedif?(m_timers[WUPDATE_AUCTIONS].Passed()){m_timers[WUPDATE_AUCTIONS].Reset();///-?Update?mails?(return?old?mails?with?item,?or?delete?them)//(tested...?works?on?win)if?(++mail_timer?>?mail_timer_expires){mail_timer?=?0;sObjectMgr.ReturnOrDeleteOldMails(true);}///-?Handle?expired?auctionssAuctionMgr.Update();}///?<li>?Handle?AHBot?operationsif?(m_timers[WUPDATE_AHBOT].Passed()){sAuctionBot.Update();m_timers[WUPDATE_AHBOT].Reset();}///?<li>?Handle?session?updatesUpdateSessions(diff);///?<li>?Handle?weather?updates?when?the?timer?has?passedif?(m_timers[WUPDATE_WEATHERS].Passed()){///-?Send?an?update?signal?to?Weather?objectsfor?(WeatherMap::iterator?itr?=?m_weathers.begin();?itr?!=?m_weathers.end();){///-?and?remove?Weather?objects?for?zones?with?no?player//?As?interval?>?WorldTickif?(!itr->second->Update(m_timers[WUPDATE_WEATHERS].GetInterval())){delete?itr->second;m_weathers.erase(itr++);}else++itr;}m_timers[WUPDATE_WEATHERS].SetCurrent(0);}///?<li>?Update?uptime?tableif?(m_timers[WUPDATE_UPTIME].Passed()){uint32?tmpDiff?=?uint32(m_gameTime?-?m_startTime);uint32?maxClientsNum?=?GetMaxActiveSessionCount();m_timers[WUPDATE_UPTIME].Reset();LoginDatabase.PExecute("UPDATE?uptime?SET?uptime?=?%u,?maxplayers?=?%u?WHERE?realmid?=?%u?AND?starttime?=?"?UI64FMTD,?tmpDiff,?maxClientsNum,?realmID,?uint64(m_startTime));}///?<li>?Handle?all?other?objects///-?Update?objects?(maps,?transport,?creatures,...)sMapMgr.Update(diff);sBattleGroundMgr.Update(diff);sOutdoorPvPMgr.Update(diff);///-?Delete?all?characters?which?have?been?deleted?X?days?beforeif?(m_timers[WUPDATE_DELETECHARS].Passed()){m_timers[WUPDATE_DELETECHARS].Reset();Player::DeleteOldCharacters();}//?execute?callbacks?from?sql?queries?that?were?queued?recentlyUpdateResultQueue();///-?Erase?corpses?once?every?20?minutes//每20分鐘清除尸體if?(m_timers[WUPDATE_CORPSES].Passed()){m_timers[WUPDATE_CORPSES].Reset();sObjectAccessor.RemoveOldCorpses();}///-?Process?Game?events?when?necessary//處理游戲事件if?(m_timers[WUPDATE_EVENTS].Passed()){m_timers[WUPDATE_EVENTS].Reset();???????????????????//?to?give?time?for?Update()?to?be?processeduint32?nextGameEvent?=?sGameEventMgr.Update();m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent);m_timers[WUPDATE_EVENTS].Reset();}///?</ul>///-?Move?all?creatures?with?"delayed?move"?and?remove?and?delete?all?objects?with?"delayed?remove"sMapMgr.RemoveAllObjectsInRemoveList();//?update?the?instance?reset?timessMapPersistentStateMgr.Update();//?And?last,?but?not?least?handle?the?issued?cli?commandsProcessCliCommands();//?cleanup?unused?GridMap?objects?as?well?as?VMapssTerrainMgr.Update(diff); }

這是World::Update函數的全部代碼,服務器循環執行這些代碼,每一次執行就能更新一次游戲世界。這個函數看似比較長,實際上不算很長,其中的關鍵之處在于首先是根據定時器來執行特定的任務,而執行這些任務則是通過調用各個模塊的Manager來完成,比如游戲世界里面的尸體每20分鐘清除一次,就檢測相關的定時器是否超時,超時則清理尸體,然后重置定時器。通過這些定時器,來執行游戲中由服務器主動完成的任務,這些任務基本上是通過定時器來啟動的。游戲中的天氣系統、PvP系統、地形系統等等都根據定時器指定的頻率進行更新。除了更新各個模塊之外,其中還有個非常重要的調用:

UpdateSessions(diff);

如果翻譯過來就是更新所有會話,服務器端為每一個客戶端建立一個Session,即會話,它是客戶端與服務端溝通的通道,取數據、發數據都得通過這條通道,這樣客戶端和服務端才能溝通。在mangos的構架中,Session的作用非常重要,但其功能不僅僅取客戶端發過來的數據、將服務端數據發給客戶端那么簡單,后面會繼續結束這個Session,很關鍵的東西,下面是UpdateSessions的具體實現:

void?World::UpdateSessions(uint32?diff) {///-?Add?new?sessionsWorldSession*?sess;while?(addSessQueue.next(sess))AddSession_(sess);///-?Then?send?an?update?signal?to?remaining?onesfor?(SessionMap::iterator?itr?=?m_sessions.begin(),?next;?itr?!=?m_sessions.end();?itr?=?next){next?=?itr;++next;///-?and?remove?not?active?sessions?from?the?listWorldSession*?pSession?=?itr->second;WorldSessionFilter?updater(pSession);if?(!pSession->Update(updater)){RemoveQueuedSession(pSession);m_sessions.erase(itr);delete?pSession;}} }

其內部結構很簡單,主要遍歷所有會話,移除不活動的會話,并調用每個Session的Update函數,達到更新所有Session的目的,有1000玩家在線就會更新1000個會話,前面提到了Session,每個會話的內部都掛載有一個消息隊列,這里隊列存儲著從客戶端發過來的數據包,1000個會話就會有1000個數據包隊列,隊列是由網絡模塊收到數據包后,將其掛載到相應Sesson的接收隊列中,客戶端1發來的數據包被掛載到Session1的隊列,客戶端2的就掛載到Session2的隊列中。mangos的架構中Session不止是收發數據的入口,同樣也是處理客戶端數據的入口,即處理客戶端請求的調度中心。每次Update Session的時候,這個Update 函數的內部會取出隊列中所有的請求數據,循環地對每一個數據包調用數據包對應的處理代碼,即根據數據包的類型(操作碼OpCode)調用相應的函數進行處理,而這些“相應的函數”是Session內部的普通成員函數,以HandleXXXXXX開頭,為了便于理解,我將Session的Update函數主體核心代碼寫在這里:

bool?WorldSession::Update(PacketFilter&?updater){///-?Retrieve?packets?from?the?receive?queue?and?call?the?appropriate?handlers///?not?process?packets?if?socket?already?closedWorldPacket*?packet?=?NULL;while?(m_Socket?&&?!m_Socket->IsClosed()?&&?_recvQueue.next(packet,?updater)){OpcodeHandler?const&?opHandle?=?opcodeTable[packet->GetOpcode()];ExecuteOpcode(opHandle,?packet);}}

這樣看起了比較清楚了,Session在Update的時候,取出所有數據包,每個數據包都有一個操作碼,opcode,魔獸模擬器有1600多個操作碼,玩家或者服務器的每個操作都有一個對應的操作碼,比如攻擊某個目標、拾取一件東西、使用某個物品都有操作碼,被追加到數據包頭部,這樣每次取數據包的操作碼,就可以查找相應的處理代碼來處理這個數據包。

從代碼里面可以看到opHandle就是根據操作碼查找到的數據處理程序,內部有相應數據處理函數的指針,ExecuteOpcode即是通過這個函數指針調用該函數來處理數據包。而處理函數實際上都是 Session的普通成員函數,當然調度處理代碼的時候并非根據操作碼進行switch判斷來調用相應處理函數,這樣會寫一個非常巨大的switch結構,mangos的方式是通過硬編碼將這些處理函數的地址存在opcodeTable這個全局的表結構中,使用OpCode作為索引,迅速地定位到相應的處理函數,即找到改數據包對應的Handler,并執行他們。

void?HandleGroupInviteOpcode(WorldPacket&?recvPacket);void?HandleGroupInviteResponseOpcode(WorldPacket&?recvPacket);void?HandleGroupUninviteOpcode(WorldPacket&?recvPacket);void?HandleGroupUninviteGuidOpcode(WorldPacket&?recvPacket);void?HandleGroupSetLeaderOpcode(WorldPacket&?recvPacket);void?HandleGroupDisbandOpcode(WorldPacket&?recvPacket);void?HandleOptOutOfLootOpcode(WorldPacket&?recv_data);void?HandleSetAllowLowLevelRaidOpcode(WorldPacket&?recv_data);void?HandleLootMethodOpcode(WorldPacket&?recvPacket);void?HandleLootRoll(WorldPacket&?recv_data);void?HandleRequestPartyMemberStatsOpcode(WorldPacket&?recv_data);void?HandleRaidTargetUpdateOpcode(WorldPacket&?recv_data);void?HandleRaidReadyCheckOpcode(WorldPacket&?recv_data);void?HandleRaidReadyCheckFinishedOpcode(WorldPacket&?recv_data);void?HandleGroupRaidConvertOpcode(WorldPacket&?recv_data);void?HandleGroupChangeSubGroupOpcode(WorldPacket&?recv_data);void?HandleGroupAssistantLeaderOpcode(WorldPacket&?recv_data);void?HandlePartyAssignmentOpcode(WorldPacket&?recv_data);

上面是極小部分的處理函數,他們都是Session的成員函數,這些函數并非是最終處理數據的,往往一個函數對應一個邏輯模塊,與這個模塊相關的操作碼有很多,比如聊天系統客戶端發來的操作碼可能是密聊、隊聊、地圖聊天,但是在Session收到數據包時,會將這個模塊的這些操作碼都調用HandleMessage函數,這些Handle函數內部會根據具體的操作碼再調用相應模塊的處理函數,就是說消息的調度是兩級的。先從入口點,通過查找OpCodeTabel找到一級調度函數、數據包傳過去后又進行二級調度,分發到更小的子模塊,直到分發的具體模塊為止。

今天暫時寫到這里,還有很多想說的,以后繼續慢慢吹,下次繼續今天沒完善的內容、談一談mangos的二進制協議、數據通信機制等內容,長期研究下mangos,肯定有好處的。

轉載于:https://my.oschina.net/u/1024573/blog/412232

總結

以上是生活随笔為你收集整理的Mangos魔兽世界服务端初探(1)--游戏服务端主体结构与消息分发的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。