魔兽私服Trinitycore架构设计分析之一

原文:

http://blog.csdn.net/yuleslie/article/details/7429887

 

这几天有些空闲时间,把改编自MangOS的魔兽私服Trinitycore代码梳理一下,也给有这方面兴趣
的童鞋一个交流空间,可能会连载15篇左右,大家慢慢看
首先把整体架构网络层说一下


打开整个工程,找到Trinitycore项目,game这个库引用了framework,shared等库,以后有时间再一一介绍

Trinitycore_build_8128_for_client_333a/src/trinitycore/Main.cpp

这个主函数其实就是提取配置文件名称和在Windows环境下以WIN32 服务方式使用的三种情况,这种编程手法
在我们很多项目中都很常见,就不多说了


最主要的就是这句

return sMaster.Run();

这里最重要的一个步骤就是
void World::SetInitialWorldSettings()
其实我们做过网游开发的人都应该知道,这就是加载服务器的各种资源文件,初始化场景,初始化脚本引擎,
序列化文件读取之类的步骤,每款游戏各有不同,但都是启动前最耗时间的一段程序

然后就是启动4个线程
ACE_Based::Thread world_thread(new WorldRunnable);
   
world_thread.setPriority(ACE_Based::Highest);

ACE_Based::Thread* cliThread = NULL;

cliThread = new ACE_Based::Thread(new CliRunnable);

ACE_Based::Thread rar_thread(new RARunnable);
FreezeDetectorRunnable *fdr = new FreezeDetectorRunnable();

接着取得IP,和端口开始网络服务
int
WorldSocketMgr::StartNetwork (ACE_UINT16 port, const char* address)
这个函数很明显,大家都可以找到,也可以很轻松找到下一个函数


int 
WorldSocketMgr::StartReactiveIO (ACE_UINT16 port, const char* address)

在分析这个函数时不得不去仔细阅读一下另一个类的声明,只看一部分就够了
typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> WorldHandler;
class WorldSocket : protected WorldHandler
{
    public:
        /// Declare some friends
        friend class ACE_Acceptor< WorldSocket, ACE_SOCK_ACCEPTOR >;
        friend class WorldSocketMgr;
        friend class ReactorRunnable;

        /// Declare the acceptor for this class
        typedef ACE_Acceptor< WorldSocket, ACE_SOCK_ACCEPTOR > Acceptor;----------这个模板定义很重要


WorldSocket 的 open 方法由接受器工厂在连接建立之后调用的时候调用

这样思路就完全清晰了

int WorldSocket::open (void *a)

函数中通过
   if (sWorldSocketMgr->OnSocketOpen (this) == -1)
----这个方法,把这个SOCKET对象放入了一个合适的线程中      
  return -1;

最后就是网络包对应逻辑处理的流程

WorldSocket::handle_input (ACE_HANDLE)
---WorldSocket::handle_input_missing_data
---WorldSocket::handle_input_payload
---WorldSocket::ProcessIncoming (WorldPacket* new_pct)
---最后进入
int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)
此函数最重要的部分,列出如下

//这句话生成了玩家的session,并保存
    ACE_NEW_RETURN (m_Session, WorldSession (id, this, AccountTypes(security), expansion, mutetime, locale), -1);

    m_Crypt.Init(&K);

    m_Session->LoadGlobalAccountData();
    m_Session->LoadTutorialsData();
    m_Session->ReadAddonsInfo(recvPacket);

    // In case needed sometime the second arg is in microseconds 1 000 000 = 1 sec
    ACE_OS::sleep (ACE_Time_Value (0, 10000));

    sWorld.AddSession (m_Session);


网络这一块真是难以用很漂亮的语言描述清楚,中间再加一个小插曲

WorldSocketMgr.cpp中定义了一个主动对象类,这个就是sWorldSocketMgr->OnSocketOpen函数中
分担各个WorldSocket的那个线程类


        virtual int svc ()
        {
            DEBUG_LOG ("Network Thread Starting");

            WorldDatabase.ThreadStart();

            ACE_ASSERT (m_Reactor);

            SocketSet::iterator i, t;

            while (!m_Reactor->reactor_event_loop_done ())
            {
                // dont be too smart to move this outside the loop
                // the run_reactor_event_loop will modify interval
                ACE_Time_Value interval (0, 10000);

                if (m_Reactor->run_reactor_event_loop (interval) == -1)
                    break;

                AddNewSockets ();

                for (i = m_Sockets.begin (); i != m_Sockets.end ();)
                {
                    if ((*i)->Update () == -1)-------------这就是int WorldSocket::Update (void)
                    {
                        t = i;
                        ++i;
                        (*t)->CloseSocket ();
                        (*t)->RemoveReference ();
                        --m_Connections;
                        m_Sockets.erase (t);
                    }
                    else
                        ++i;
                }
            }

            WorldDatabase.ThreadEnd();

            DEBUG_LOG ("Network Thread Exitting");

            return 0;
        }

然而在AddNewSockets ()中,他把这一批次生成的新连接保存到另一个叫作

m_Sockets的数据成员中,
并把起初保存WorldSocket的m_NewSockets清空
而正是由于在下面这个函数中m_NewSockets被填充

        int AddSocket (WorldSocket* sock)
       
{
           
  ACE_GUARD_RETURN (ACE_Thread_Mutex, Guard, m_NewSockets_Lock, -1);

           
  ++m_Connections;
           
  sock->AddReference();
           
  sock->reactor (m_Reactor);
           
  m_NewSockets.insert (sock);

          
   return 0;
       
}

导火索又回到了

int
WorldSocketMgr::OnSocketOpen (WorldSocket* sock)
{

....


    for (size_t i = 1; i < m_NetThreadsCount; ++i)
       
if (m_NetThreads[i].Connections () < m_NetThreads[min].Connections ())
           
  min = i;

   
    return m_NetThreads[min].AddSocket (sock);
}


最后,我们进入最关键的类World

还记得刚才描述过的流程吗在这一步

int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)
里面生成了玩家的Session

    ACE_NEW_RETURN (m_Session, WorldSession (id, this, AccountTypes(security), expansion, mutetime, locale), -1);
.....
    sWorld.AddSession (m_Session);

注意有一个插曲

void World::AddSession(WorldSession* s)

{
  

addSessQueue.add(s);
//先暂时放到了 ACE_Based::LockedQueue<WorldSession*, ACE_Thread_Mutex> addSessQueue;
}


而后再下面这个函数中

void World::UpdateSessions(uint32 diff)
{
    ///- Add new sessions
    WorldSession* sess;
    while (addSessQueue.next(sess))
        AddSession_ (sess); //又通过AddSession_ 将WorldSession*保存到了一个typedef UNORDERED_MAP<uint32, WorldSession*> SessionMap;
  SessionMap m_sessions 这样类型的容器中;

    ///- 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
        if (!itr->second->Update(diff))                      // As interval = 0
        {
            if (!RemoveQueuedPlayer(itr->second) && itr->second && getConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
                m_disconnects[itr->second->GetAccountId()] = time(NULL);
            delete itr->second;
            m_sessions.erase(itr);
        }
    }
}
其实这种先放到一个,再放入一个正式的容器,其实是为了踢号用的


最后指出几个地方,这是处理逻辑的

world的update在此调用


/// Heartbeat for the World
void 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 = getMSTime();

    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 = getMSTime();

        uint32 diff = getMSTimeDiff(realPrevTime,realCurrTime);

        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.KickAll();                                       // save and kick all players
    sWorld.UpdateSessions( 1 );                             // real players unload required UpdateSessions call

    // unload battleground templates before different singletons destroyed
    sBattleGroundMgr.DeleteAllBattleGrounds();

    sWorldSocketMgr->StopNetwork();

    MapManager::Instance().UnloadAll();                     // unload all grids (including locked in memory)

    ///- End the database thread
    WorldDatabase.ThreadEnd();                                  // free mySQL thread resources
}


worldsession 的update在

/// Update the World !
void World::Update(uint32 diff)
{
    m_updateTime = uint32(diff);
    if (m_configs[CONFIG_INTERVAL_LOG_UPDATE])
    {
        if (m_updateTimeSum > m_configs[CONFIG_INTERVAL_LOG_UPDATE])
        {
            sLog.outBasic("Update time diff: %u. Players online: %u.", m_updateTimeSum / m_updateTimeCount, GetActiveSessionCount());
            m_updateTimeSum = m_updateTime;
            m_updateTimeCount = 1;
        }
        else
        {
            m_updateTimeSum += m_updateTime;
            ++m_updateTimeCount;
        }
    }

    ///- 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();

    /// Handle daily quests reset time
    if (m_gameTime > m_NextDailyQuestReset)
    {
        ResetDailyQuests();
        m_NextDailyQuestReset += DAY;
    }

    if (m_gameTime > m_NextWeeklyQuestReset)
        ResetWeeklyQuests();

    /// <ul><li> Handle auctions when the timer has passed
    if (m_timers[WUPDATE_AUCTIONS].Passed())
    {
        auctionbot.Update();
        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;
            objmgr.ReturnOrDeleteOldMails(true);
        }

        ///- Handle expired auctions
        auctionmgr.Update();
    }

    /// <li> Handle session updates when the timer has passed
    RecordTimeDiff(NULL);
    UpdateSessions(diff);-----------------------------------------------------在这里
    RecordTimeDiff("UpdateSessions");

    /// <li> Handle weather updates when the timer has passed
    if (m_timers[WUPDATE_WEATHERS].Passed())
    {
        m_timers[WUPDATE_WEATHERS].Reset();

        ///- Send an update signal to Weather objects
        WeatherMap::iterator itr, next;
        for (itr = m_weathers.begin(); itr != m_weathers.end(); itr = next)
        {
            next = itr;
            ++next;

            ///- 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);
            }
        }
    }
    /// <li> Update uptime table
    if (m_timers[WUPDATE_UPTIME].Passed())
    {
        uint32 tmpDiff = (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> Clean logs table
    if (sWorld.getConfig(CONFIG_LOGDB_CLEARTIME) > 0) // if not enabled, ignore the timer
    {
        if (m_timers[WUPDATE_CLEANDB].Passed())
        {
            //uint32 tmpDiff = (m_gameTime - m_startTime);
            //uint32 maxClientsNum = sWorld.GetMaxActiveSessionCount();

            m_timers[WUPDATE_CLEANDB].Reset();
            loginDatabase.PExecute("DELETE FROM logs WHERE (time + %u) < "UI64FMTD";",
                sWorld.getConfig(CONFIG_LOGDB_CLEARTIME), uint64(time(0)));
        }
    }

    /// <li> Handle all other objects
    ///- Update objects when the timer has passed (maps, transport, creatures,...)
    MapManager::Instance().Update(diff);                // As interval = 0

    /*if (m_timers[WUPDATE_OBJECTS].Passed())
    {
        m_timers[WUPDATE_OBJECTS].Reset();
        MapManager::Instance().DoDelayedMovesAndRemoves();
    }*/

    static uint32 autobroadcaston = 0;
    autobroadcaston = sConfig.GetIntDefault("AutoBroadcast.On", 0);
    if (autobroadcaston == 1)
    {
       if (m_timers[WUPDATE_AUTOBROADCAST].Passed())
       {
          m_timers[WUPDATE_AUTOBROADCAST].Reset();
          SendRNDBroadcast();
       }
    }

    sBattleGroundMgr.Update(diff);
    RecordTimeDiff("UpdateBattleGroundMgr");

    sOutdoorPvPMgr.Update(diff);
    RecordTimeDiff("UpdateOutdoorPvPMgr");

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

    ///- Erase corpses once every 20 minutes
    if (m_timers[WUPDATE_CORPSES].Passed())
    {
        m_timers[WUPDATE_CORPSES].Reset();

        CorpsesErase();
    }

    ///- 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 = gameeventmgr.Update();
        m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent);
        m_timers[WUPDATE_EVENTS].Reset();
    }

    // update the instance reset times
    sInstanceSaveManager.Update();

    // And last, but not least handle the issued cli commands
    ProcessCliCommands();
}

 

你可能感兴趣的:(魔兽私服Trinitycore架构设计分析之一)