MaNGOS-Zero源码学习之mangosd(三):WorldSession服务器会话

时间:2024-02-21 20:43:40

    上篇《socket的处理方式》介绍了mangosd是如何接收和管理socket的,而在MaNGOS中客户端的所有游戏操作都是通过消息传递与游戏逻辑服务器进行交互的,比如玩家角色移动,与NPC对话,领取任务,释放技能等等。

    消息传递在mangosd上以会话的形式维护,在客户端通过服务器的验证之后,mangosd会主动建立并保持一个与客户端连接的会话。所有的消息传递在该会话里进行。

 

一、会话的建立

    会话建立过程如下代码: 

   1: int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)
   2: {
   3:     ........//以上是客户端身份验证的代码,成功则执行下面的操作
   4:  
   5:     // NOTE ATM the socket is single-threaded, have this in mind ...
   6:     //(1)
   7:     ACE_NEW_RETURN (m_Session, WorldSession (id, this, AccountTypes(security), mutetime, locale), -1);
   8:  
   9:     //(2)
  10:     m_Crypt.SetKey (K.AsByteArray(), 40 );
  11:     m_Crypt.Init ();
  12:  
  13:     m_Session->LoadTutorialsData();
  14:  
  15:     // In case needed sometime the second arg is in microseconds 1 000 000 = 1 sec
  16:     ACE_OS::sleep (ACE_Time_Value (0, 10000));
  17:  
  18:     //(3)
  19:     sWorld.AddSession (m_Session);
  20:  
  21:     // Create and send the Addon packet
  22:     if (sAddOnHandler.BuildAddonPacket (&recvPacket, &SendAddonPacked))
  23:         SendPacket (SendAddonPacked);
  24:  
  25:     return 0;
  26: }

 

(1)创建一个WorldSession* m_Session对象,参数含义分别是:账户id,WorldSocket对象(用来获得连接的socket及对socket的操作),帐号安全级别,禁言时间,区域设置。

(2)初始化加密成员变量m_Crypt,把sessionkey设进去,随后的所有数据交互都用RC4加密。

(3)把session添加到World对象的session队列中。

 

 

二、session的数据交互

    游戏主线程会不断的调用World::UpdateSessions ()来更新session执行相关的操作。 

   1: void World::UpdateSessions( uint32 diff )
   2: {
   3:     ///- Add new sessions
   4:     WorldSession* sess;
   5:     while(addSessQueue.next(sess)) //------------(1)
   6:         AddSession_ (sess);
   7:  
   8:     ///- Then send an update signal to remaining ones
   9:     for (SessionMap::iterator itr = m_sessions.begin(), next; itr != m_sessions.end(); itr = next)
  10:     {
  11:         next = itr;
  12:         ++next;
  13:         ///- and remove not active sessions from the list
  14:         WorldSession * pSession = itr->second;
  15:         WorldSessionFilter updater(pSession);
  16:  
  17:         if(!pSession->Update(updater))//---------(2)
  18:         {
  19:             RemoveQueuedSession(pSession);
  20:             m_sessions.erase(itr);
  21:             delete pSession;
  22:         }
  23:     }
  24: }

 

(1)有新会话则加入到hash_map<uint32, WorldSession*> m_sessions;的成语变量中。

(2)调用每个会话的Update() 函数

 

    再来看看WorldSession::Update()

   1: bool WorldSession::Update(PacketFilter& updater)
   2: {
   3:     WorldPacket* packet;
   4:     while (m_Socket && !m_Socket->IsClosed() && _recvQueue.next(packet, updater))
   5:     {
   6:         OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()];
   7:         try
   8:         {
   9:             switch (opHandle.status)
  10:             {
  11:                 case STATUS_LOGGEDIN:
  12:                     //...............
  13:                     break;
  14:                 case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT:
  15:                     //...............
  16:                     break;
  17:                 case STATUS_TRANSFER:
  18:                     //...............
  19:                     break;
  20:                 case STATUS_AUTHED:
  21:                     //...............
  22:                     break;
  23:                 case STATUS_NEVER:
  24:                     //...............
  25:                     break;
  26:                 case STATUS_UNHANDLED:
  27:                     //...............
  28:                     break;
  29:                 default:
  30:                     sLog.outError("SESSION: received wrong-status-req opcode %s (0x%.4X)",
  31:                         opHandle.name,
  32:                         packet->GetOpcode());
  33:                     break;
  34:             }
  35:         }
  36:         catch (ByteBufferException &)
  37:         {
  38:             //...............
  39:         }
  40:  
  41:         delete packet;
  42:     }
  43:  
  44:     //...............
  45:  
  46:     return true;
  47: }
 
    可以看到Update() 中主要从_recvQueue中取出数据包packet,然后再opcodeTable的对应表中找到对应的处理函数,然后根据不同的状态进行不同的处理。opcodeTable表的设计也比较中规中矩,后续文章会有记录。接下来看_recvQueue中的数据是从哪来的
 
    _recvQueue的数据是通过WorldSession::QueuePacket() 函数添加的。 
   1: /// Add an incoming packet to the queue
   2: void WorldSession::QueuePacket(WorldPacket* new_packet)
   3: {
   4:     _recvQueue.add(new_packet);
   5: }

   

    WorldSession::QueuePacket() 函数由下列函数调用,从而往_recvQueue队列里添加数据:

   1: int WorldSocket::handle_input (ACE_HANDLE)
   2: {
   3:     ........
   4:     switch (handle_input_missing_data ())
   5:     ........
   6: }
   7:             |
   8:             |
   9:            \|/
  10: int WorldSocket::handle_input_missing_data (void)
  11: {
  12:     ........
  13:     //just received fresh new payload
  14:     if (handle_input_payload () == -1)
  15:     ........
  16: }
  17:             |
  18:             |
  19:            \|/
  20: int WorldSocket::handle_input_payload (void)
  21: {
  22:     ........
  23:     const int ret = ProcessIncoming (m_RecvWPct);
  24:     ........
  25: }
  26:             |
  27:             |
  28:            \|/
  29: int WorldSocket::ProcessIncoming (WorldPacket* new_pct)
  30: {
  31:     ........
  32: }

 

    接下来看WorldSocket::ProcessIncoming() 函数是怎么处理的:

   1: int WorldSocket::ProcessIncoming (WorldPacket* new_pct)
   2: {
   3:     ........
   4:     const ACE_UINT16 opcode = new_pct->GetOpcode ();
   5:     if (opcode >= NUM_MSG_TYPES)
   6:     {
   7:         sLog.outError( "SESSION: received nonexistent opcode 0x%.4X", opcode);
   8:         return -1;
   9:     }
  10:  
  11:     try
  12:     {
  13:         switch(opcode)
  14:         {
  15:         case CMSG_PING:
  16:             return HandlePing (*new_pct);
  17:  
  18:         case CMSG_AUTH_SESSION:
  19:             if (m_Session)
  20:             {
  21:                 sLog.outError ("WorldSocket::ProcessIncoming: Player send CMSG_AUTH_SESSION again");
  22:                 return -1;
  23:             }
  24:             return HandleAuthSession (*new_pct);
  25:  
  26:         case CMSG_KEEP_ALIVE:
  27:             DEBUG_LOG ("CMSG_KEEP_ALIVE ,size: "SIZEFMTD" ", new_pct->size ());
  28:             return 0;
  29:  
  30:         default:
  31:             {
  32:                 ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1);
  33:  
  34:                 if (m_Session != NULL)
  35:                 {
  36:                     // OK ,give the packet to WorldSession
  37:                     aptr.release ();
  38:                     // WARNINIG here we call it with locks held.
  39:                     // Its possible to cause deadlock if QueuePacket calls back
  40:                     m_Session->QueuePacket (new_pct); //---------------(1)
  41:                     return 0;
  42:                 }
  43:                 else
  44:                 {
  45:                     sLog.outError ("WorldSocket::ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
  46:                     return -1;
  47:                 }
  48:             }
  49:         }
  50:     }
  51:     catch (ByteBufferException &)
  52:     {
  53:         ........
  54:     }
  55:  
  56:     ACE_NOTREACHED (return 0);
  57: }

 

(1)如果不是CMSG_PING,CMSG_AUTH_SESSION,CMSG_KEEP_ALIVE这几个消息则将数据加入消息队列。

 

 

三、总结: 

  1. mangosd和客户端之间通过TCP交换数据,使用Session作为服务器端的抽象,封装交互的处理方法。
  2. 在Session中Player *_player;是服务器端player的指针,WorldSocket *m_Socket;则封装TCP服务器端socket的相关操作。
  3. 使用Session做抽象,可以把具体逻辑操作和socket操作分开,一个client对应一个Session也便于管理,发生交互时_player指针不需要直接操作socket,降低耦合低。
  4. WorldSession::Update() 对WorldPacket* packet;的不同状态做了集中的处理,这样的状态机简洁,代码集中也便于查找和维护。