MaNGOS-Zero源码学习之realmd认证登录服务器(二):socket的处理方式_aa8621090的博客-程序员秘密

技术标签: 数据库  

    在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

转载于:https://www.cnblogs.com/ychellboy/archive/2011/12/10/2283456.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/aa8621090/article/details/101549599

智能推荐

第三周 项目四 年龄几何_jiaqingyan1的博客-程序员秘密

问题及代码:/* * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:qiudengcha数列.cpp * 作者: 贾庆严 * 完成日期:2016年3月10日 * 版本号:V1.0 * * 问题描述:张三,李四,王五,刘六,年龄为等差数列,四人年龄和为26,乘积为880 * 程序输入:无 * 程序

Unit Test -- 初步了解_wakeup_qin的博客-程序员秘密

什么是单元测试在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。 —— [ 维基百科 ]

mysql from_unixtime(_Mysql中的FROM_UNIXTIME实现按天查询_而东且西的博客-程序员秘密

近期做了一套战绩系统,战绩要按天查询,每天赢了多少分,打了多少局都要列出来。可是,怎么用sql进行按天分组呢?首先,我们的表里要确认已经有了时间戳(int(10)),我这里用的是create_time。接下来,开始编辑语句FROM_UNIXTIME函数支持转换时间戳为字符串,我们把时间戳转换为日期FROM_UNIXTIME( create_time, '%Y年%m月%d日' )然后,根据这个日期分...

OpenCV-Python实战(番外篇)——利用 K-Means 聚类进行色彩量化_盼小辉丶的博客-程序员秘密

K-Means 聚类算法的目标是将 n 个样本划分(聚类)为 K 个簇,该算法通过找到簇的中心并将输入样本分组到簇周围。在本文中,将利用 K-Means 聚类进行色彩量化,以减少图像中颜色数量,以探索更多 K-Means 聚类算法的可能应用。

删除自带python版本后系统崩溃,死性不改重新配环境。_mengsmq的博客-程序员秘密

装了好几个版本的python,想清理一下,手贱把ubuntu内置的python给卸载了,卸载的时候突突突显示一大堆包被卸载,同时侧栏软件一个一个消失。没敢重启,马上安装了桌面,图标又回来了,但是还是许多命令报错。联想到这个系统让我折腾的乱七八糟,不折腾了,干脆重装。安装系统,20.04.01LTSubuntu内就有启动U盘制作app,下个iso插入u盘制作即可。引导,选不下载更新,安装,进入桌面,设置阿里源,再更新。sudo apt-get updatesudo apt-get upgrade

随便推点

Cannot modify session.sql_log_bin inside a transaction_翔云123456的博客-程序员秘密

目录问题解决方案参考问题当主库上数据的某些变更,不希望复制到从库上时,经常会将sql_log_bin关闭,使更新操作不记录bin log。例如,在一个连接会话开始的时候,关闭sql_log_bin,接下来,这个连接上执行的更新操作都不记录bing log。为了保证,关闭sql_log_bin和更新操作,是在同一个连接上,我们会想到使用事务transaction。但在使用事务过程中,遇到如下报错:Error 1694: Cannot modify @@session.sql_log_bin ins

【kettle教程(二)--表字段数据为集合数据--附超详细示例】_吕秀儿的博客-程序员秘密_kettle 集合

文章目录前言一、场景描述二、开始搞事1.前置流程大家伙应该都知道了, 我就一图带过了.总结前言这里不做过多前置场景描述, 铁子们有需要可参考上一篇博文:【kettle使用教程(一)–复制单表–附超详细示例】一、场景描述表一:字段 area_info 数据示例:[ { "areaId":"110000000000", "type":"province", "title":"北京市" }, { "areaId

matlab 结构体存入数组,matlab之结构体数组struct_独厨食记的博客-程序员秘密

以下内容来自于:https://blog.csdn.net/u010999396/article/details/54413615/要在MALTAB中实现比较复杂的编程,就不能不用struct类型。而且在MATLAB中实现struct比C中更为方便。4. 3.1 结构数组的创建MATLAB提供了两种定义结构的方式:直接法和使用struct函数法。1. 使用直接引用方式定义结构与建立数值型数组一样,...

如何让自己像一个专业人士:兼谈应试学习和能力学习 2012-03-20 17:00:34_aquanga的博客-程序员秘密

如何让自己像一个专业人士:兼谈应试学习和能力学习2012-03-20 17:00:34      我又造出了两个不算概念的概念,意思很明确,按照学习的目的划分,有一类学习就是为了考试、考证,还有一类学习是为了提高自己的能力。      我这么说可能有些人会不同意,学习能提高能力么?不是在工作和实际操作中才能提高能力么?对这个问题稍后进行阐述。先说说为什么把学习分作这两类。

Qt事件处理_Yolk、的博客-程序员秘密

Qt事件的处理:1.事件有两种调度方式: 同步和异步​ Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环,先处理Qt事件队列中的事件, 直至为空,再处理系统消息队列中的消息, 直至为空, 处理系统消息的时候会产生新的Qt事件, 需要对其再次进行处理。​ 调用QApplication::sendEvent的时候, 消息会立即被处理,是同步的。实际上 QApplication::sendEvent()是通过调用QApplication::notify(

自定义Button形状(圆形、椭圆) shape_变身小甜甜的博客-程序员秘密

2011-09-19 08:36 30147人阅读 评论(0) 收藏 举报转自:http://blog.csdn.net/xyylchq/article/details/6788761一开始想到的是自定义View,倒腾了好几天,也没弄好,主要是卡在自定义View的点击事件。在查自定义