一、概述
想看下开源的服务器框架,本以为挺复杂,但mangos代码写的很清楚。mangos不是一个魔兽私服模拟器,它是一个开源的自由软件项目,是用c++和C#编程语言,实现的一个支持大型多人在线角色扮演游戏服务器的程序框架。svn的路径:http://svn.code.sf.net/p/mangos/code/trunk 下载下来貌似有100多兆,我用的vs2005编译vc8工程release版本一次就编译过了。
主目录中文件夹有:
contrib 第三方的工具
dep 依赖的开源库ace sqlite等
src 项目代码
sql 数据库脚本
src目录下文件夹有:
bindings文件夹中包含脚本文件,应该是对脚本进行绑定的。
framework文件夹中包括一些游戏框架,其中包括网络框架,游戏系统框架,工具,平台等内容。
game文件夹中应该是游戏的文件,包括世界系统,战斗系统,游戏事件,游戏场景等的实现。
mangosd文件夹中是mangosd的主程序,包括程序的入口等。
realmd 文件夹中是游戏区域信息,包括RealmList等内容。
shared文件夹中 应该是公用的函数和库,database的内容包含在其中。
线程分布:
1、主线程 main—- 主要功能:初始化world、创建子线程、回收资源
2、WorldRunnable ——-主线程
3、CliRunnable —–调试线程 command line
4、RARunnable ——-Remote Administration 处理远程管理命令?
5、MaNGOSsoapRunnable—协议
6、FreezeDetectorRunnable —- 心跳检测
7、SqlDelayThread — 数据线程
8、PatcherRunnable —- 给客户端升级(发送补丁文件)
这里对于线程类的命名都是以Runnable开始,以继承的方式实现线程类,从而对线程的分布一目了然,并且对类有一定说明作用。
事件分发和处理:
WorldRunnable::run—World:update—-World:UpdateSessions—WorldSession::Update(一个socket内所有事件)—各种各样的handler
二、WorldRunnable类
/// 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::m_stopEvent)
{
++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;
ZThread::Thread::sleep(prevSleepTime);
}
else
prevSleepTime = 0;
}
... // 清理资源
}
这是游戏世界的驱动线程,一开始很困惑“Heartbeat”难道是用来保持长连接用的?其实这里的Heartbeat应该理解为是整个世界的驱动的地方,像人的心脏,汽车的发动机之类。
WorldDatabase.ThreadStart();这里名字很明确,“ThreadStart”,线程启动,如果内部带线程运行,名字中最好体现,不要叫做“Start”之类。同样结束线程名字为WorldDatabase.ThreadEnd();
在线程中,每次处理完都sleep了一段时间,有注释如下
// 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
这可能是游戏服务器中的特殊的部分,游戏服务器不需要“实时”处理用户的请求,只需要让用户觉得是“实时”的就够了。就像电视,不论是液晶的合适CRT的都有个刷新频率。而且如果使用“实时”处理的方式还会引入很多不必要的问题,例如,如果实时处理没有sleep,假设服务器能够处理,用户通过某种方法,在1秒内发送了1000次的“出拳”指令,如果不加以处理,那么一碰到这拳头其他人就挂了。这个固定的处理时间,也给整个游戏世界定了一个时间的最小片段,动作频率的最小片段,方便以后各种业务的处理。
sleep会不会浪费cpu呢?不会,因为只有在线程能够在一个WORLD_SLEEP_CONST处理完所有操作时候才会sleep,如果这个处理线程一直是满负荷的,那么这个线程也会一直工作。
计算方法看着比较复杂,其实不难。不考虑其他情况,假如线程每次都能处理完(diff<=WORLD_SLEEP_CONST+prevSleepTime),
那么有DO = d0 + t0, d1 = WORLD_SLEEP_CONST + d0 - D0 = WORLD_SLEEP_CONST - t0;
那么假设WORLD_SLEEP_CONST为100ms,处理花费10ms(t0),那么就sleep90ms(100 - 10)就可以了;然后,diff = 10 + 90 = 100ms,这种理想的情况下,diff一直是100ms,但是在线程很忙的时候,sWorld.Update时间大于100ms时候,也就是diff>WORLD_SLEEP_CONST+prevSleepTime <==> t0 + d0 > WORLD_SLEEP_CONST+prevSleepTime(d0),即t0 > WORLD_SLEEP_CONST时候,diff就不等100了,这个时候prevSleepTime,diff等于sWorld.Update。