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如下:
类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