mangos0.9源码分析学习笔记(二)

当mangos开始执行到  sMaster.Run()时,开始运行Master对象 ,至此,Mangos开始所构建的游戏服务器才算真正开始运行。

sMaster其实是Master类的一个实例,sMaster MaNGOS::Singleton::Instance(),应该是使用了单例模式来实现的,设计模式这块不是很清楚。

Master类的主要作用是启动Server服务器,Run()函数为该类的执行入口。

该函数主要处理如下工作:

1,输出一些版本等信息;

2,开始启动数据库的连接,连接三个数据库,这三个数据库就是在main函数中所声明的三个全局DatabaseMysq,包括WorldDatabase游戏世界数据库,CharacterDatabase游戏角色数据库,loginDatabase登录信息数据库.

3,进行游戏世界的初始化,即sWorld  (#define sWorld MaNGOS::Singleton::Instance())的初始化。

4,从配置中获取通讯端口,生成一个socket句柄,并将监听Socket与该端口进行绑定, 这一步是做通讯的初始化处理 。

5,对程序的中止信号进行捕捉 ,捕捉中断信号,其实主要是设置World::m_stopEvent,该参数非常重要,主要用于世界更新中判断是否需要更新服务器。

6,启动WorldRunnable线程,并设置Server的登录状态,该线程会调用WorldRunnable::run(),该函数用于世界信息的更新,服务器检查从客户端发送来的信息,并且根据信息进行游戏世界的更新,应该是是核心中的核心。

7,启动CliRunnable线程  // 如果配置中启动了命令行模式,则使用该线程,用于在服务器执行过程中处理命令行信息

8,启动RAListenSocket    // 作用暂时未知

9 ,主服务器的主循环,判断当World::m_stopEvent不为真时,即服务器未收到中止信号时,进行如下几个处理

10,当服务器World::m_stopEvent为真时,开始结束服务做各种断开服务连接的准备工作等等,并且清空各种ScriptingModule。

代码较长,贴出部分代码如下:

/// Master 开始执行入口
void Master::Run()
{
	// 输出版本信息和其他一些信息 
    sLog.outString( "MaNGOS daemon %s", _FULLVERSION );
    sLog.outString( " to stop.\n\n" );

    sLog.outTitle( "MM   MM         MM   MM  MMMMM   MMMM   MMMMM");
    sLog.outTitle( "MM   MM         MM   MM MMM MMM MM  MM MMM MMM");
    sLog.outTitle( "MMM MMM         MMM  MM MMM MMM MM  MM MMM");
    sLog.outTitle( "MM M MM         MMMM MM MMM     MM  MM  MMM");
    sLog.outTitle( "MM M MM  MMMMM  MM MMMM MMM     MM  MM   MMM");
    sLog.outTitle( "MM M MM M   MMM MM  MMM MMMMMMM MM  MM    MMM");
    sLog.outTitle( "MM   MM     MMM MM   MM MM  MMM MM  MM     MMM");
    sLog.outTitle( "MM   MM MMMMMMM MM   MM MMM MMM MM  MM MMM MMM");
    sLog.outTitle( "MM   MM MM  MMM MM   MM  MMMMMM  MMMM   MMMMM");
    sLog.outTitle( "        MM  MMM http://www.mangosproject.org");
    sLog.outTitle( "        MMMMMM\n\n");

    // 开始启动数据库连接,若数据库启动失败,则退出run(),mangosd运行结束
    if (!_StartDB())  // 
        return;  

    ///初始化游戏世界 其中 sWorld定义如下:   也是使用的MaNGOS::Singleton进行实例化
	// #define sWorld MaNGOS::Singleton::Instance()   sWorld 被定义为一个这样的宏,挺方便的
    sWorld.SetInitialWorldSettings();

    //获取世界通讯端口号 
    port_t wsport = sWorld.getConfig(CONFIG_PORT_WORLD);

    SocketHandler h;  // 实例化一个Socket句柄

    ListenSocket worldListenSocket(h);  // 创建 监听Socket类
	// 将该Socket监听类绑定到世界通讯的端口上  若绑定无错误,则返回0 否则返回错误代码
    if (worldListenSocket.Bind(wsport))
    {
        clearOnlineAccounts();
        sLog.outError("MaNGOS cannot bind to port %d", wsport);
        return;  // 套接字绑定出错,程序返回
    }

    h.Add(&worldListenSocket);  // 将绑定好的套接字增加到之前声明的Socket句柄上

    //捕捉程序中止信号,判断是否中止服务
    _HookSignals();

    //启动WorldRunnable线程 该线程会调用WorldRunnable::run(),该函数用于世界信息的更新,服务器检查从客户端发送来的信息,
    //并且根据信息进行游戏世界的更新,应该是是核心中的核心。
    ZThread::Thread t(new WorldRunnable);
    t.setPriority ((ZThread::Priority )2);

    // 然后设置Server的登录状态,设置分区号码realmID
    loginDatabase.PExecute("UPDATE `realmlist` SET `color` = 0, `population` = 0 WHERE `id` = '%d'",realmID);

#ifdef WIN32
    if (sConfig.GetBoolDefault("Console.Enable", 1) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
#else
    if (sConfig.GetBoolDefault("Console.Enable", 1))
#endif
    {
        //若配置文件中设置了命令行模式,则启动命令行处理进程,用于处理在服务器运行过程中的命令输入,类似于处理终端输入的GM命令 
        ZThread::Thread td1(new CliRunnable);
    }

    ///启动RAlisten  这部分没有想明白是做什么,以后在细看
    ListenSocket RAListenSocket(h);
    if (sConfig.GetBoolDefault("Ra.Enable", 0))
    {
        port_t raport = sConfig.GetIntDefault( "Ra.Port", 3443 );
        std::string stringip = sConfig.GetStringDefault( "Ra.IP", "0.0.0.0" );
        ipaddr_t raip;
        if(!Utility::u2ip(stringip, raip))
            sLog.outError( "MaNGOS RA can not bind to ip %s", stringip.c_str());
        else if (RAListenSocket.Bind(raip, raport))
            sLog.outError( "MaNGOS RA can not bind to port %d on %s", raport, stringip.c_str());
        else
        {
            h.Add(&RAListenSocket);

            sLog.outString("Starting Remote access listner on port %d on %s", raport, stringip.c_str());
        }
    }

    uint32 realCurrTime, realPrevTime;
    realCurrTime = realPrevTime = getMSTime();

    uint32 socketSelecttime = sWorld.getConfig(CONFIG_SOCKET_SELECTTIME);

    uint32 numLoops = (sConfig.GetIntDefault( "MaxPingTime", 30 ) * (MINUTE * 1000000 / socketSelecttime));
    uint32 loopCounter = 0;

    ///- 主循环,当世界信息中m_stopEvent不为真,即未接到中止信号时,执行更新循环,判断当前时序,根据监听端口获得到的客户端信息,对游戏世界进行更新,并且通过心跳机制保证与数据库的连接。
    while (!World::m_stopEvent)
    {
        if (realPrevTime > realCurrTime)
            realPrevTime = 0;
        // 判断游戏更新时间,根据时间进行服务器更新
        realCurrTime = getMSTime();
        sWorldSocketMgr.Update( realCurrTime - realPrevTime );
        realPrevTime = realCurrTime;
        // 核心中的核心函数处理Socket信息,使用WorldSocket::OnRead函数
        h.Select(0, socketSelecttime);
        
        // 应该属于心跳机制,保持与数据库的连接处于激活状态
        if( (++loopCounter) == numLoops )
        {
            loopCounter = 0;
            sLog.outDetail("Ping MySQL to keep connection alive");
            delete WorldDatabase.Query("SELECT 1 FROM `command` LIMIT 1");
            delete loginDatabase.Query("SELECT 1 FROM `realmlist` LIMIT 1");
            delete CharacterDatabase.Query("SELECT 1 FROM `bugreport` LIMIT 1");
        }
    }
    // 当接收到中止信号之后,即m_stopEvent为真以后,处理服务器关闭相关事宜。
    // 设置服务器状态为离线状态 之前设置 color为0 为在线 
    loginDatabase.PExecute("UPDATE `realmlist` SET `color` = 2 WHERE `id` = '%d'",realmID);

     _UnhookSignals();

    // when the main thread closes the singletons get unloaded
    // since worldrunnable uses them, it will crash if unloaded after master
    t.wait();
    clearOnlineAccounts();   ///- 服务器关闭,清除在线的帐号
    CharacterDatabase.HaltDelayThread();  // 角色数据库延迟处理
    WorldDatabase.HaltDelayThread();  // 世界数据库
    loginDatabase.HaltDelayThread(); // 登录数据库

    sLog.outString( "Halting process..." );

    UnloadScriptingModule();

    return;
}

主线就是这样的,以后会根据主线慢慢的读进去,自己也是刚刚开始接触这代码,感觉挺有意思的。

在run()中,输出相关信息后,要做的第一件事情就是连接数据库,使用_StartDB()方法对数据库进行连接,数据库的地址和用户名密码信息均从配置文件中读取,可能这也是单机版的问题,实际中的不清楚应该怎么做。

bool Master::_StartDB()函数代码如下:

// 进行数据库连接的初始化
bool Master::_StartDB()
{  
	// 读Config文件,从中获得到世界数据数据库的配置信息 
	// 我下到的源码自带的配置文件中世界数据字符串描述文件如下:
	//"127.0.0.1;3306;mangos;mangos;mangos"  可以用来配置单机测试
    std::string dbstring; // 记录数据库字符串
    if(!sConfig.GetString("WorldDatabaseInfo", &dbstring))
    {
		// 无法获取到数据库配置信息则 连接出错返回
        sLog.outError("Database not specified in configuration file");
        return false;
    }
    sLog.outString("World Database: %s", dbstring.c_str());

    ///- 连接worldData数据库,其中全局变量WorldDatabase为之前在main函数中定义的DatabaseMysql类
    if(!WorldDatabase.Initialize(dbstring.c_str()))
    {
        sLog.outError("Cannot connect to world database %s",dbstring.c_str());
        return false;
    }
	// 获取角色数据库 默认配置为"127.0.0.1;3306;mangos;mangos;characters" 
    if(!sConfig.GetString("CharacterDatabaseInfo", &dbstring))
    {
        sLog.outError("Character Database not specified in configuration file");
        return false;
    }
    sLog.outString("Character Database: %s", dbstring.c_str());

    //连接到角色数据库,可以获取到角色的信息 使用的是之前main中定义的CharacterDatabase全局变量
    if(!CharacterDatabase.Initialize(dbstring.c_str()))
    {
        sLog.outError("Cannot connect to Character database %s",dbstring.c_str());
        return false;
    }

    ///从配置文件中获取登录信息数据库的信息 
    if(!sConfig.GetString("LoginDatabaseInfo", &dbstring))
    {
        sLog.outError("Login database not specified in configuration file");
        return false;
    }

    //连接到登录信息数据库,这里应该是记录用户的账户信息,具体数据库的内容以后会详细分析的
    sLog.outString("Login Database: %s", dbstring.c_str() );
    if(!loginDatabase.Initialize(dbstring.c_str()))
    {
        sLog.outError("Cannot connect to login database %s",dbstring.c_str());
        return false;
    }
    //从配置文件中获取到游戏分区的信息
    realmID = sConfig.GetIntDefault("RealmID", 0);
    if(!realmID)
    {
        sLog.outError("Realm ID not defined in configuration file");
        return false;
    } 
    sLog.outString("Realm running as realm ID %d", realmID); //输出一些分区信息 ? 

    //该函数用来清楚数据库中的角色在线信息
  //当一个帐号中拥有角色时,连接数据库后设置该分区内该帐号下的角色在线标志为0,此处暂时为弄明白0代表什么
   clearOnlineAccounts();

  // 从世界数据库中获取到版本信息 并且检测版本是否正确
    QueryResult* result = WorldDatabase.Query("SELECT `version` FROM `db_version` LIMIT 1");
    if(result)
    {
        Field* fields = result->Fetch();

        sLog.outString("Using %s", fields[0].GetString()); 
        delete result;
    }
    else
        sLog.outString("Using unknown world database.");  // 若版本错误则只输出提示信息

    return true;
}

连接数据库之后是游戏世界的初始化。以后会顺着这个思路一点一点分析其实现的。

 

 

你可能感兴趣的:(mangos,数据库,database,socket,character,delete,服务器)