• bigworld源码分析(3)——dbMgr分析


      dbMgr主要是玩家数据的读取和保存的,例如在bigworld源码分析(3)中,玩家在认证的时候,loginApp需要通过dbMgr来验证玩家数据是否合法,这就是针对玩家的账号数据进行查询。本篇中,我们主要针对以下几个问题来分析dbMgr工作原理。

      (1) dbMgr如何验证玩家的账号合法性 

      (2) dbMgr是如何读取玩家的游戏数据的

      (3) dbMgr如何通知baseAppMgr创建entity

      (4) dbMgr是如何保存玩家的游戏数据的

      1. dbMgr验证玩家账号

      dbMgr验证玩家账号的调用关系,入下图所示。

      

       (1) DataBase在收到了logOn的消息之后,进行一些条件限制和判断之后,直接调用LoginHandler处理login

    oid Database::logOn( const Mercury::Address & srcAddr,
            Mercury::ReplyID replyID,
            LogOnParamsPtr pParams,
            const Mercury::Address & addrForProxy,
            bool offChannel )
    {
        // 判断能否进行认证
            // .......
    
        LoginHandler * pHandler =
            new LoginHandler( pParams, addrForProxy, srcAddr, offChannel, replyID );
    
        pHandler->login();
    }

      (2) LoginHandler的login方法如下:

    void LoginHandler::login()
    {
        // __glenc__ TODO: See if this needs to be converted to use params
        Database::instance().getIDatabase().mapLoginToEntityDBKey(
            pParams_->username(), pParams_->password(), *this );
    
        // When mapLoginToEntityDBKey() completes, onMapLoginToEntityDBKeyComplete()
        // will be called.
    }

      从代码中可以看出是通过getIDatabase方法返回的IDataBase接口,调用其mapLoginToEntityDBKey。 那么纠结是使用了哪个IDataBase实现类呢?在DataBase的init中我们可以看出来,主要可能是3中实现IDataBase接口的类。其类图关系如下。

      主要是XMLDataBase,OracleDatabase和MySqlDatabase,也就是说bigworld提供了这3种数据存取方式。其实只有2中,XML和Mysql。我们这里主要看Mysql方式。

      (3) (4)(5)MapLoginToEntityDBKeyTask调用typeMapping.getLogOnMapping

    void MapLoginToEntityDBKeyTask::run()
    {
        bool retry;
        do
        {
            retry = false;
            MySqlThreadData& threadData = this->getThreadData();
            try
            {
                MySqlTransaction transaction( threadData.connection );
                std::string actualPassword;
                bool entryExists = threadData.typeMapping.getLogOnMapping( transaction,
                    logOnName_, actualPassword, threadData.ekey.typeID,
                    threadData.ekey.name );
                // .....
        } while (retry);
    }

      (6) MySqlTypeMapping::getLogOnMapping

    bool MySqlTypeMapping::getLogOnMapping( MySqlTransaction& t, const std::string& logOnName,
            std::string& password, EntityTypeID& typeID, std::string& recordName )
    {
        boundLogOnName_.setString( logOnName );
        t.execute( stmtGetLogOnMapping_ );
        // ......
    }

      可以看出来,这里的execute就是真正执行sql语句的地方,那么这个stmtGetLogOnMapping_ 是什么呢,其实就是一个简单的sql语句的封装,

    MySqlTypeMapping::MySqlTypeMapping( MySql& con, const EntityDefs& entityDefs,
            const char * tableNamePrefix ) :
        mappings_(),
        // ....
        stmtGetLogOnMapping_( con, "SELECT m.password, t.bigworldID, m.recordName "
                    "FROM bigworldLogOnMapping m, bigworldEntityTypes t "
                    "WHERE m.logOnName=? and m.typeID=t.typeID" ),

      在MySqlTypeMapping的构造函数中,我们可以看出stmtGetLogOnMapping_具体是一个什么样的sql语句。其实这里就是简单的将mysql业务,和sql语句直接简单做了一个映射关系,也就可以理解MySqlTypeMapping中TypeMapping的意思了,就是根据业务类型做了映射而已。

      到这里,我们基本就知道dbMgr是如何对账号进行认证的了。

      在MapLoginToEntityDBKeyTask::run执行完之后,会执行MapLoginToEntityDBKeyTask::onRunComplete(),那么就会在最后回调LoginHandler的onMapLoginToEntityDBKeyComplete,那么整个认证就结束了。

      2. dbMgr获取玩家数据

      在验证完玩家登陆login信息之后,如果成功,则需要从数据库中将玩家数据获取出来。在验证玩家登陆信息的时候,同时也从bigworldLogOnMapping 表中,将玩家的id获取到了,这个是后面读取玩家数据需要的key。具体的读取流程如下:

      

      (1) (2)(3)(4)LoginHandler::onMapLoginToEntityDBKeyComplete中直接一层一层调用getEntity接口,没有复杂逻辑。

      (5) (6)在GetEnitityTask中调用GetLogOnRecord,然后真正获取玩家数据,如果获取成功,最后再回调LoginHandler的onGetEntityCompleted

      

      3. dbMgr通知baseAppMgr创建entity

      LoginHandler::onMapLoginToEntityDBKeyComplete中,在getEntity之前,DataBase中的接口就开始通知BaseAppMgr去创建Entity了,甚至这个时候还不知道Entity能否创建成功。

      

    void LoginHandler::onMapLoginToEntityDBKeyComplete( DatabaseLoginStatus status,
                                                       const EntityDBKey& ekey )
    {
        bool shouldLoadEntity = false;
        bool shouldCreateEntity = false;
    
        if (status == DatabaseLoginStatus::LOGGED_ON)
        {
            ekey_ = ekey;
            shouldLoadEntity = true;
            state_ = StateWaitingForLoad;
        }
        .......
    
      // 可以load玩家数据
    if (shouldLoadEntity) { // Start "create new base" message even though we're not sure entity // exists. This is to take advantage of getEntity() streaming properties // into the bundle directly.
        // 这个接口里就直接通知BaseAppMgr去创建玩家的entity。 pStrmDbID_ = Database::prepareCreateEntityBundle( ekey_.typeID, ekey_.dbID, clientAddr_, this, bundle_, pParams_ ); // Get entity data pBaseRef_ = &baseRef_; outRec_.provideBaseMB( pBaseRef_ ); // Get entity mailbox outRec_.provideStrm( bundle_ ); // Get entity data into bundle Database::instance().getEntity( *this ); // When getEntity() completes, onGetEntityCompleted() is called. } ....... }

      Database::prepareCreateEntityBundle函数代码。

      

    /*
     *    This method inserts the "header" info into the bundle for a
     *    BaseAppMgrInterface::createEntity message, up till the point
     *    where entity properties should begin.
     *
     *    @return    If dbID is 0, then this function returns the position in the
     *    bundle where you should put the DatabaseID.
     */
    DatabaseID* Database::prepareCreateEntityBundle( EntityTypeID typeID,
            DatabaseID dbID, const Mercury::Address& addrForProxy,
            Mercury::ReplyMessageHandler* pHandler, Mercury::Bundle& bundle,
            LogOnParamsPtr pParams )
    {
        bundle.startRequest( BaseAppMgrInterface::createEntity, pHandler, 0,
            Mercury::DEFAULT_REQUEST_TIMEOUT + 1000000 ); // 1 second extra
    
        ......
    }

      从这个函数和其注释,我们可以看出来,这里并没有真正的向BaseAppMgr去请求createEntity,而只是准备让它创建,具体发送这个指令,需要entity的属性准备完毕。

      最后LoginHandler::sendCreateEntityMsg调用DataBase的send接口,将createEntity的消息发送给BaseAppMgr。这个中间发生了哪些事情,最后调用了sendCreateEntityMsg呢?调用关系如下图:

      

      从上面的调用关系图可以看出,onGetEntityCompleted中可能会发生3中情况

      (1) 在玩家数据不存在的情况下,需要创建新的玩家,也就是createNewEntity

      (2) 需要对登陆数据进行映射,也就是setLoginMapping

      (3) 需要校验玩家数据checkOutEntity

      不过他们最终都是会回调到LoginHandler::sendCreateEntityMsg中!!

       4. dbMgr是如何保存玩家的游戏数据的

      dbMgr还有一个重要的功能就是保持玩家数据,相对于获取玩家数据,保存玩家数据流程相对更加简单,其流程如下:

      

      DataBase中writeEntity调用WriteEntityHandler的writeEntity接口,然后后面还是调用MysqlDatabase的putEntity接口,最后回调WriteEntityHandler的finalise接口,在这个接口里面判断,是否需要返回保存结果。其代码如下:

    void WriteEntityHandler::finalise( bool isOK )
    {
      // 如果需要返回保存结果
    if (shouldReply_) { Mercury::ChannelSender sender( Database::getChannel( srcAddr_ ) ); sender.bundle().startReply( replyID_ ); sender.bundle() << isOK << ekey_.dbID; } if (isOK && (flags_ & WRITE_LOG_OFF)) {
        // 如果是玩家下线保存 Database::instance().onEntityLogOff( ekey_.typeID, ekey_.dbID ); }
    delete this; }

      

      至此,dbMgr的登录验证,玩家数据获取,玩家数据保存,以及如何通知baseAppMgr创建entity这4个我们一开始列出来的点,都已经基本分析清楚了

  • 相关阅读:
    Windows常用内容渗透命令
    字符串编码小记
    卡巴斯基命令解析
    聚合与继承
    Maven与Eclipse整合
    maven系列三:Maven核心概念
    使用Maven构建项目
    Maven项目构建过程练习
    maven系列二:Maven入门
    使用Maven构建多模块项目
  • 原文地址:https://www.cnblogs.com/chobits/p/5076596.html
Copyright © 2020-2023  润新知