网易首页 > 网易号 > 正文 申请入驻

wow魔兽世界服务端主体结构

0
分享至

wow魔兽世界服务端主体结构

服务端主要由三大块组成,数据库、服务端逻辑、脚本。数据库用的MySQL,这里不是很关键暂且不说。脚本有自己的脚本引擎,简单的任务、战斗等都可以通过数据库配置相应条目来完成,复杂的战斗AI等在脚本库中由C++直接写成,这个脚本库是要被编译为机器代码的,执行效率相当高效,例如巫妖王的战斗比较复杂就用C++写,其它简单的就配置在数据库中由脚本引擎来驱动执行。AZ服务端是一个多线程、逻辑单线程的服务端。每个线程内部都采用循环结构,主线程启动后将创建多个工作线程,主要包括负责游戏世界运作的核心线程,具有处理用户请求,执行定时器的能力。其它几个工作线程还有网络Io,该线程启动后其内部将使用线程池进行网络Io操作,不间断地接收数据包,并存储到相关玩家的消息队列中,由世界线程进行处理,其它几个工作线程先不讨论,看mangos的源代码.务端启动后这些线程将永不停息地工作。世界线程是服务器的核心,负责处理所有玩家操作请求,定时器、AI等。以下是世界线程启动后执行的代码:

/// Heartbeat for the Worldvoid WorldRunnable::run(){ ///- Init new SQL thread for the world database WorldDatabase.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 world while (!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 - D0 if (diff <= WORLD_SLEEP_CONST + prevSleepTime) { prevSleepTime = WORLD_SLEEP_CONST + prevSleepTime - diff; ACE_Based::Thread::Sleep(prevSleepTime); } else prevSleepTime = 0;#ifdef WIN32 if (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 thread WorldDatabase.ThreadEnd(); // free mySQL thread resources}sWorld.Update(diff);void World::Update(uint32 diff){ ///- Update the different timers for (int i = 0; i < WUPDATE_COUNT; ++i) { if (m_timers[i].GetCurrent() >= 0) m_timers[i].Update(diff); else m_timers[i].SetCurrent(0); } ///- Update the game time and check for shutdown time _UpdateGameTime(); ///-Update mass mailer tasks if any sMassMailMgr.Update(); /// Handle daily quests reset time if (m_gameTime > m_NextDailyQuestReset) ResetDailyQuests(); /// Handle weekly quests reset time if (m_gameTime > m_NextWeeklyQuestReset) ResetWeeklyQuests(); /// Handle monthly quests reset time if (m_gameTime > m_NextMonthlyQuestReset) ResetMonthlyQuests(); /// Handle monthly quests reset time if (m_gameTime > m_NextCurrencyReset) ResetCurrencyWeekCounts(); /// if (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 auctions sAuctionMgr.Update(); } /// if (m_timers[WUPDATE_AHBOT].Passed()) { sAuctionBot.Update(); m_timers[WUPDATE_AHBOT].Reset(); } /// /// if (m_timers[WUPDATE_WEATHERS].Passed()) { ///- Send an update signal to Weather objects for (WeatherMap::iterator itr = m_weathers.begin(); itr != m_weathers.end();) { ///- and remove Weather objects for zones with no player // As interval > WorldTick if (!itr->second->Update(m_timers[WUPDATE_WEATHERS].GetInterval())) { delete itr->second; m_weathers.erase(itr++); } else ++itr; } m_timers[WUPDATE_WEATHERS].SetCurrent(0); } /// if (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)); } /// ///- Update objects (maps, transport, creatures,...) sMapMgr.Update(diff); sBattleGroundMgr.Update(diff); sOutdoorPvPMgr.Update(diff); ///- Delete all characters which have been deleted X days before if (m_timers[WUPDATE_DELETECHARS].Passed()) { m_timers[WUPDATE_DELETECHARS].Reset(); Player::DeleteOldCharacters(); } // execute callbacks from sql queries that were queued recently UpdateResultQueue(); ///- 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 processed uint32 nextGameEvent = sGameEventMgr.Update(); m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent); m_timers[WUPDATE_EVENTS].Reset(); } /// ///- Move all creatures with "delayed move" and remove and delete all objects with "delayed remove" sMapMgr.RemoveAllObjectsInRemoveList(); // update the instance reset times sMapPersistentStateMgr.Update(); // And last, but not least handle the issued cli commands ProcessCliCommands(); // cleanup unused GridMap objects as well as VMaps sTerrainMgr.Update(diff);}UpdateSessions(diff);void World::UpdateSessions(uint32 diff){ ///- Add new sessions WorldSession* sess; while (addSessQueue.next(sess)) AddSession_(sess); ///- Then send an update signal to remaining ones for (SessionMap::iterator itr = m_sessions.begin(), next; itr != m_sessions.end(); itr = next) { next = itr; ++next; ///- and remove not active sessions from the list WorldSession* pSession = itr->second; WorldSessionFilter updater(pSession); if (!pSession->Update(updater)) { RemoveQueuedSession(pSession); m_sessions.erase(itr); delete pSession; } }}bool WorldSession::Update(PacketFilter& updater){ ///- Retrieve packets from the receive queue and call the appropriate handlers /// not process packets if socket already closed WorldPacket* packet = NULL; while (m_Socket && !m_Socket->IsClosed() && _recvQueue.next(packet, updater)) { OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()]; ExecuteOpcode(opHandle, packet); }}

这里先作一下说明,这是世界线程的根循环结构,在while(!World::IsStopped())内部只有一个核心函数调用,其他都是一些控制更新时间之类的代码,不用太关注:

sWorld是单一实例的World对象,它代表了整个游戏世界,和多数MMORPG一样,启动后进入根循环,在运行内部一直调用更新整个游戏世界的Update函数,服务端不停的Update游戏世界,每次Update能在100毫秒内完成,则客户端会感到非常流畅。在根循环退出后,清理服务器相关资源,线程结束被回收。

到这里仅仅需要关注一个函数了,就是World的Update方法内部到底在干什么?

  • Handle auctions when the timer has passed
Handle AHBot operations
Handle session updates UpdateSessions(diff);
Handle weather updates when the timer has passed
Update uptime table
Handle all other objects

这是World::Update函数的全部代码,服务器循环执行这些代码,每一次执行就能更新一次游戏世界。这个函数看似比较长,实际上不算很长,其中的关键之处在于首先是根据定时器来执行特定的任务,而执行这些任务则是通过调用各个模块的Manager来完成,比如游戏世界里面的尸体每20分钟清除一次,就检测相关的定时器是否超时,超时则清理尸体,然后重置定时器。通过这些定时器,来执行游戏中由服务器主动完成的任务,这些任务基本上是通过定时器来启动的。游戏中的天气系统、PvP系统、地形系统等等都根据定时器指定的频率进行更新。除了更新各个模块之外,其中还有个非常重要的调用:

如果翻译过来就是更新所有会话,服务器端为每一个客户端建立一个Session,即会话,它是客户端与服务端沟通的通道,取数据、发数据都得通过这条通道,这样客户端和服务端才能沟通。在mangos的构架中,Session的作用非常重要,但其功能不仅仅取客户端发过来的数据、将服务端数据发给客户端那么简单,后面会继续结束这个Session,很关键的东西,下面是UpdateSessions的具体实现:

其内部结构简单,主要遍历所有会话,移除不活动的会话,并调用每个Session的Update函数,达到更新所有Session的目的,有1000玩家在线就会更新1000个会话,前面提到了Session,每个会话的内部都挂载有一个消息队列,这里队列存储着从客户端发过来的数据包,1000个会话就会有1000个数据包队列,队列是由网络模块收到数据包后,将其挂载到相应Sesson的接收队列中,客户端1发来的数据包被挂载到Session1的队列,客户端2的就挂载到Session2的队列中。mangos的架构中Session不止是收发数据的入口,同样也是处理客户端数据的入口,即处理客户端请求的调度中心。每次Update Session的时候,这个Update 函数的内部会取出队列中所有的请求数据,循环地对每一个数据包调用数据包对应的处理代码,即根据数据包的类型(操作码OpCode)调用相应的函数进行处理,而这些“相应的函数”是Session内部的普通成员函数,以HandleXXXXXX开头,为了便于理解,将Session的Update函数主体核心代码写在这里:

这样看起了比较清楚了,Session在Update的时候,取出所有数据包,每个数据包都有一个操作码,opcode,魔兽模拟器有1600多个操作码,玩家或者服务器的每个操作都有一个对应的操作码,比如攻击某个目标、拾取一件东西、使用某个物品都有操作码,被追加到数据包头部,这样每次取数据包的操作码,就可以查找相应的处理代码来处理这个数据包。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
老黄摊牌,把天赐交给大女儿?直言姐妹关系好未来顺其自然,祝福

老黄摊牌,把天赐交给大女儿?直言姐妹关系好未来顺其自然,祝福

叮当当科技
2026-06-24 15:41:44
刚刚,重挫近800点,银价已“腰斩”!“到底问题出在哪里”?

刚刚,重挫近800点,银价已“腰斩”!“到底问题出在哪里”?

金融界
2026-06-24 13:41:38
太阳报:消息人士对热刺有意M费表示怀疑;罗马诺:西汉姆联将把M费出售给出价最高的俱乐部

太阳报:消息人士对热刺有意M费表示怀疑;罗马诺:西汉姆联将把M费出售给出价最高的俱乐部

MUREDS
2026-06-25 02:41:01
价格战之下,千亿补贴也救不了的车市真相

价格战之下,千亿补贴也救不了的车市真相

大佬灼见
2026-06-22 11:18:11
破防了!土耳其排协体面送别老将,中国女排功勋,只剩默默退场

破防了!土耳其排协体面送别老将,中国女排功勋,只剩默默退场

金毛爱女排
2026-06-24 00:00:07
央企“最牛女副处长”落马:两年与上司开房410次,细节曝光

央企“最牛女副处长”落马:两年与上司开房410次,细节曝光

西门老爹
2025-12-16 15:35:31
天津高考“一分一段”炸了:680分以上197人,600分以上已破万人

天津高考“一分一段”炸了:680分以上197人,600分以上已破万人

皮蛋儿电影
2026-06-24 09:59:07
中国110亿方水被邻国拦截建水库,规模超三峡

中国110亿方水被邻国拦截建水库,规模超三峡

揭秘世间万象
2026-06-11 14:59:12
昔日当红女星盖丽丽,早年的复古造型写真

昔日当红女星盖丽丽,早年的复古造型写真

娱你同欢
2026-06-24 20:40:59
你们都是什么时候对男女之事开窍的?网友:果然还是拦不住有心人

你们都是什么时候对男女之事开窍的?网友:果然还是拦不住有心人

夜深爱杂谈
2026-02-21 21:37:02
保姆给中风父亲洗澡,每次都要两小时,我打开监控一看,果断报警

保姆给中风父亲洗澡,每次都要两小时,我打开监控一看,果断报警

千秋文化
2026-05-27 20:02:00
山西沁源县委原书记李丁夫主动投案,此前煤矿爆炸事故致82死

山西沁源县委原书记李丁夫主动投案,此前煤矿爆炸事故致82死

上观新闻
2026-06-24 20:56:11
有人捡漏,有人囤签!谁是2026选秀大会首轮的最大赢家?

有人捡漏,有人囤签!谁是2026选秀大会首轮的最大赢家?

砚底沉香
2026-06-24 17:20:54
田家英为啥讨厌戚本禹呢?

田家英为啥讨厌戚本禹呢?

怪味历史连连看
2026-06-24 17:22:57
胡歌带老婆孩子重庆度假,一家人吃火锅,早在10年前就在重庆买房

胡歌带老婆孩子重庆度假,一家人吃火锅,早在10年前就在重庆买房

草莓解说体育
2026-06-25 03:22:31
新规要求演员必须用原名不得用艺名,这下几位大明星有点尴尬了

新规要求演员必须用原名不得用艺名,这下几位大明星有点尴尬了

林轻吟
2026-06-24 11:34:08
东风-17亮剑,31国全安静了?中国这一手,藏着2个没说破的警告

东风-17亮剑,31国全安静了?中国这一手,藏着2个没说破的警告

李健政观察
2026-06-22 17:20:15
娜然辱华言论曝光,霍家婚讯紧急刹车,郭晶晶一句话把门堵死了

娜然辱华言论曝光,霍家婚讯紧急刹车,郭晶晶一句话把门堵死了

往史过眼云烟
2026-06-22 16:48:30
我定居日本20年,娶过3个妻子,发现日本的女人都有一个共同特点

我定居日本20年,娶过3个妻子,发现日本的女人都有一个共同特点

千秋文化
2026-05-20 20:33:05
茜茜公主:身高173腰围50仍在减肥,因为她能拿得出手的只有美貌

茜茜公主:身高173腰围50仍在减肥,因为她能拿得出手的只有美貌

浩渺青史
2026-06-24 16:59:35
2026-06-25 05:39:00
艾西
艾西
一千个人眼里有一千个哈姆雷特
201文章数 46关注度
往期回顾 全部

游戏要闻

三国望神州:徐晃先遣实测报告+抽取价值分析!和那谁是不有点像

头条要闻

特朗普:不接受美伊协议包含任何涉及航运的费用

头条要闻

特朗普:不接受美伊协议包含任何涉及航运的费用

体育要闻

字母哥,会把凯尔特人拆了吗?

娱乐要闻

向佐向佑兄弟合体直播!母子终于和解

财经要闻

逃税23亿:审计署年报直指七家机构

科技要闻

豆包专业版上线:定价68-500元每月

汽车要闻

施鹏泽:为什么奥迪E7X强调座舱气味安全?

态度原创

游戏
手机
时尚
旅游
本地

猎魂世界:霍雨浩六大配置+操作问题分析!第一神控是否名副其实

手机要闻

REDMI K90至尊版定档,骁龙8至尊版+风冷散热

那些搞砸高考的年轻人,人生完蛋了吗?

旅游要闻

昆明闹市藏短巷,曾遍开赤红鹦哥花,一头贡象改了整条街名!

本地新闻

2026世界杯全勤太难?这份保姆级攻略请收好

无障碍浏览 进入关怀版