MaNGOS-Zero源码学习之realmd认证登录服务器(二):socket的处理方式

    在MaNGOS-Zero中使用ACE库来处理网络IO,先看一下realmd工程下的Main.cpp。经过简化后main()函数中和socket相关的代码可以表示为:

   1: int main()
   2: {
   3:     .........
   4:     
   5:     ACE_Reactor::instance(new ACE_Reactor(new ACE_TP_Reactor(), true), true);
   6:  
   7:     ///- Launch the listening network socket
   8:     ACE_Acceptor<AuthSocket, ACE_SOCK_Acceptor> acceptor;
   9:  
  10:     uint16 rmport = sConfig.GetIntDefault("RealmServerPort", DEFAULT_REALMSERVER_PORT);
  11:     std::string bind_ip = sConfig.GetStringDefault("BindIP", "0.0.0.0");
  12:     ACE_INET_Addr bind_addr(rmport, bind_ip.c_str());
  13:  
  14:     if(acceptor.open(bind_addr, ACE_Reactor::instance(), ACE_NONBLOCK) == -1)
  15:     {
  16:         sLog.outError("MaNGOS realmd can not bind to %s:%d", bind_ip.c_str(), rmport);
  17:         Log::WaitBeforeContinueIfNeed();
  18:         return 1;
  19:     }
  20:  
  21:     .........
  22:  
  23:     ///- Wait for termination signal
  24:     while (!stopEvent)
  25:     {
  26:         // dont move this outside the loop, the reactor will modify it
  27:         ACE_Time_Value interval(0, 100000);
  28:  
  29:         if (ACE_Reactor::instance()->run_reactor_event_loop(interval) == -1)
  30:             break;
  31:  
  32:         if( (++loopCounter) == numLoops )
  33:         {
  34:             loopCounter = 0;
  35:             DETAIL_LOG("Ping MySQL to keep connection alive");
  36:             LoginDatabase.Ping();
  37:         }
  38: #ifdef WIN32
  39:         if (m_ServiceStatus == 0) stopEvent = true;
  40:         while (m_ServiceStatus == 2) Sleep(1000);
  41: #endif
  42:     }
  43:  
  44:     .........
  45:  
  46:     sLog.outString( "Halting process..." );
  47:     return 0;
  48: }

 

    在讲解代码前,需要对ACE的连接器及各个连接器的处理顺序做一个说明:

    realmd采用ACE接受器-连接器模式(详细见这里),ACE中接收器主要有ACE_Acceptor, ACE_Svc_Handler, ACE_Reactor 3个主要类组成。ACE_Reactor是分发器(Dispatcher), ACE_Acceptor创建出ACE_Svc_Handler。处理顺序如下:

1. ACE_Acceptor的open将自身帮定到ACE_Reactor上,并向其注册:当在PEER_ADDR上发生ACCEPT事件,调用handle_input成员挂钩函数。

2. 主程序调用ACE_Reactor的handle_events()时,检测到ACCEPT,调用ACE_Acceptor的handle_input()。在Handle_Input中继续调用虚函数make_svc_handle()构造出ACE_Svc_Handler类(可以新建,则每客户一个Handler,也可使用单例,则多个客户共用一个服务处理器)。接着调用accept_ svc_handle(),将具体的参数传给ACE_Svc_Handler。最后调用active_ svc_handle(),一般调用ACE_Svc_Handler的open函数。在Open函数中注册反应器事件,如必要调用active()创建出线程。

    一般把创建接收器的线程称为主线程,把运行Ace_Reactor的handle_events()的线程取名为事件分发线程。把运行ACE_Svc_Handler的svc()的线程叫做服务线程。这些线程根据实现不同会有以下几种组合。[1]

 

    回到realmd的代码,可以清晰的看出main函数里主要干了这么几件事:

    (1)在main函数的开始使用ACE_Reactor::instance();指定需要使用的Reactor类型。

    (2)接着指定Acceptor的类型:ACE_Acceptor<AuthSocket, ACE_SOCK_Acceptor> acceptor;并用open()函数绑定到先前指定的Reactor上。

    (3)最后执行事件循环函数ACE_Reactor::instance()->run_reactor_event_loop(interval);

 

    在Main.cpp完成ACE的注册和绑定之后,当有网络IO事件到来时,主要的处理都集中在class AuthSocket上,对应的Class View如下:

image     类AuthSocket继承至BufferedSocket类,并实现BufferedSocket的两个虚函数:

   1: class AuthSocket: public BufferedSocket
   2: {
   3:     ......
   4:     void OnAccept();
   5:     void OnRead();
   6:     ......
   7: };
   8:  
   9: class BufferedSocket : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>
  10: {
  11:     ......
  12:     virtual void OnRead(void)   { }
  13:     virtual void OnAccept(void) { }
  14:     virtual void OnClose(void)  { }
  15:     ......
  16: };

   

1. 处理新连接的到来 

    可以看到BufferedSocket类继承至ACE_Svc_Handler,当client发送connect请求的时候,ACE的Reactor模式会执行下面的调用流程:

   1: ACE_Acceptor::handle_input()
   2: {
   3:     ......
   4:     else if (this->activate_svc_handler (svc_handler) == -1) {......}
   5:     ......    
   6: }           
   7:             |
   8:             |
   9:            \|/
  10: ACE_Acceptor::activate_svc_handler(SVC_HANDLER *svc_handler)
  11: {
  12:     ......
  13:     if (result == 0 && svc_handler->open ((void *) this) == -1)
  14:         result = -1;
  15:     ......
  16: }
  17:             |
  18:             |
  19:            \|/
  20: int BufferedSocket::open(void * arg)
  21: {
  22:     ......
  23:     this->OnAccept();
  24:     ......
  25: }

由此调用void AuthSocket::OnAccept()函数对新到来的连接进行处理。

 

 

2. 处理连接已经建立socket的数据读写

    连接建立后到socket可读时ACE的Reactor会主动调用int BufferedSocket::handle_input(ACE_HANDLE)函数,进而调用AuthSocket::OnRead()函数,在AuthSocket::OnRead()函数中,实现不同协议号的消息到不同处理函数的映射:

   1: void AuthSocket::OnRead()
   2: {
   3:     ......
   4:     for (i = 0; i < AUTH_TOTAL_COMMANDS; ++i)
   5:     {
   6:         if ((uint8)table[i].cmd == _cmd &&
   7:             (table[i].status == STATUS_CONNECTED ||
   8:             (_authed && table[i].status == STATUS_AUTHED)))
   9:         {
  10:             ......
  11:             if (!(*this.*table[i].handler)())
  12:             {
  13:                 DEBUG_LOG("Command handler failed for cmd %u recv length %u",
  14:                         (uint32)_cmd, (uint32)recv_len());
  15:                  return;
  16:             }
  17:             break;
  18:         }        
  19:     }
  20: }

上面这段代码主要是处理在不同状态(STATUS_CONNECTED、STATUS_AUTHED)下,将不同协议号的消息到不同处理函数,而映射规则就在table[]的数组里:

   1: const AuthHandler table[] =
   2: {
   3:     { CMD_AUTH_LOGON_CHALLENGE,     STATUS_CONNECTED, &AuthSocket::_HandleLogonChallenge    },
   4:     { CMD_AUTH_LOGON_PROOF,         STATUS_CONNECTED, &AuthSocket::_HandleLogonProof        },
   5:     { CMD_AUTH_RECONNECT_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleReconnectChallenge},
   6:     { CMD_AUTH_RECONNECT_PROOF,     STATUS_CONNECTED, &AuthSocket::_HandleReconnectProof    },
   7:     { CMD_REALM_LIST,               STATUS_AUTHED,    &AuthSocket::_HandleRealmList         },
   8:     { CMD_XFER_ACCEPT,              STATUS_CONNECTED, &AuthSocket::_HandleXferAccept        },
   9:     { CMD_XFER_RESUME,              STATUS_CONNECTED, &AuthSocket::_HandleXferResume        },
  10:     { CMD_XFER_CANCEL,              STATUS_CONNECTED, &AuthSocket::_HandleXferCancel        }
  11: };

 

这样的处理方式在协议号比较少的情况下是不错的选择,因为这种写法使得代码集中,什么状态下能处理什么消息、有那个函数来处理都一目了然。但是当需要维护的状态很多,同时状态之间的转化比较频繁时,考虑使用State模式应该会更好些,当然使用State模式同样会带来一些新的问题,比如:状态转化时数据传递的问题。这不在本篇的讨论范围之内,先不予展开。

 

总结:

 

    realmd使用ACE的Reactor模式来处理socket连接,

优点:使得逻辑处理和连接管理分开,把连接管理部分全部交给ACE来做。带来的好处是显而易见的,开发者只需要专注于realmd认证逻辑处理及SRP6算法的编写。

缺点:ACE的文档相当的少,接口使用起来也不是这么方便…………

 

References:

[1]http://hi.baidu.com/99916742/blog/item/08f74cfcff7beb4dd6887ddf.html

posted @ 2011-12-10 18:10  ychellboy  阅读(3030)  评论(0编辑  收藏  举报