艾西 发表于 2024-6-8 15:38:54

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

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
}
这里先作一下说明,这是世界线程的根循环结构,在while(!World::IsStopped())内部只有一个核心函数调用,其他都是一些控制更新时间之类的代码,不用太关注:
sWorld.Update(diff);
sWorld是单一实例的World对象,它代表了整个游戏世界,和多数MMORPG一样,启动后进入根循环,在运行内部一直调用更新整个游戏世界的Update函数,服务端不停的Update游戏世界,每次Update能在100毫秒内完成,则客户端会感到非常流畅。在根循环退出后,清理服务器相关资源,线程结束被回收。
到这里仅仅需要关注一个函数了,就是World的Update方法内部到底在干什么?
void World::Update(uint32 diff)
{
    ///- Update the different timers
    for (int i = 0; i < WUPDATE_COUNT; ++i)
    {
      if (m_timers.GetCurrent() >= 0)
            m_timers.Update(diff);
      else
            m_timers.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();
    /// <ul><li> Handle auctions when the timer has passed
    if (m_timers.Passed())
    {
      m_timers.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();
    }

    /// <li> Handle AHBot operations
    if (m_timers.Passed())
    {
      sAuctionBot.Update();
      m_timers.Reset();
    }

    /// <li> Handle session updates    UpdateSessions(diff);

    /// <li> Handle weather updates when the timer has passed
    if (m_timers.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.GetInterval()))
            {
                delete itr->second;
                m_weathers.erase(itr++);
            }
            else
                ++itr;
      }

      m_timers.SetCurrent(0);
    }
    /// <li> Update uptime table
    if (m_timers.Passed())
    {
      uint32 tmpDiff = uint32(m_gameTime - m_startTime);
      uint32 maxClientsNum = GetMaxActiveSessionCount();

      m_timers.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 before
    if (m_timers.Passed())
    {
      m_timers.Reset();
      Player::DeleteOldCharacters();
    }

    // execute callbacks from sql queries that were queued recently    UpdateResultQueue();

    ///- Erase corpses once every 20 minutes
    //每20分钟清除尸体
    if (m_timers.Passed())
    {
      m_timers.Reset();

      sObjectAccessor.RemoveOldCorpses();
    }

    ///- Process Game events when necessary
    //处理游戏事件
    if (m_timers.Passed())
    {
      m_timers.Reset();                   // to give time for Update() to be processed
      uint32 nextGameEvent = sGameEventMgr.Update();
      m_timers.SetInterval(nextGameEvent);
      m_timers.Reset();
    }

    /// </ul>
    ///- 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);
}
这是World::Update函数的全部代码,服务器循环执行这些代码,每一次执行就能更新一次游戏世界。这个函数看似比较长,实际上不算很长,其中的关键之处在于首先是根据定时器来执行特定的任务,而执行这些任务则是通过调用各个模块的Manager来完成,比如游戏世界里面的尸体每20分钟清除一次,就检测相关的定时器是否超时,超时则清理尸体,然后重置定时器。通过这些定时器,来执行游戏中由服务器主动完成的任务,这些任务基本上是通过定时器来启动的。游戏中的天气系统、PvP系统、地形系统等等都根据定时器指定的频率进行更新。除了更新各个模块之外,其中还有个非常重要的调用:
UpdateSessions(diff);
如果翻译过来就是更新所有会话,服务器端为每一个客户端建立一个Session,即会话,它是客户端与服务端沟通的通道,取数据、发数据都得通过这条通道,这样客户端和服务端才能沟通。在mangos的构架中,Session的作用非常重要,但其功能不仅仅取客户端发过来的数据、将服务端数据发给客户端那么简单,后面会继续结束这个Session,很关键的东西,下面是UpdateSessions的具体实现:
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;
      }
    }
}
其内部结构简单,主要遍历所有会话,移除不活动的会话,并调用每个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 closed
    WorldPacket* packet = NULL;
    while (m_Socket && !m_Socket->IsClosed() && _recvQueue.next(packet, updater))
    {
      OpcodeHandler const& opHandle = opcodeTable;
      ExecuteOpcode(opHandle, packet);
    }
}
这样看起了比较清楚了,Session在Update的时候,取出所有数据包,每个数据包都有一个操作码,opcode,魔兽模拟器有1600多个操作码,玩家或者服务器的每个操作都有一个对应的操作码,比如攻击某个目标、拾取一件东西、使用某个物品都有操作码,被追加到数据包头部,这样每次取数据包的操作码,就可以查找相应的处理代码来处理这个数据包。


页: [1]
查看完整版本: wow魔兽世界服务端主体结构