这几天有些空闲时间,把改编自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();
}