转载时请附上文章原著者地址: http://blog.csdn.net/qq51931373/article/details/29817029
在分析dragon的登录服务器结构时,本人喜欢从组成这个LoginServer功能的众多对象入手,研究这些对象的功能和对象之间如何组织关系来达到数据处理和数据存储的井然有序,就能很好的看清楚他的思维,从别人的代码中领悟出为自己所用的经验出来.
而dragon的代码很好的做到了这一点,让程序结构非常清晰,这点带来的好处就是不容易出错,服务器很稳定,而且他的结构就是我们服务器编程中常见的Actor模型.与 Actor 模型相对应的模型则是我们在面向对象编程中使用的 Object 模型,Object 模型中宣扬,一切皆为 Object(对象),而 Actor 模型则认为一切皆为 Actor。Actor 模型中,Actor 之间通过消息相互通信,这是其和 Object 模型的一个显著的区别,换而言之 Actor 模型使用消息传递机制来代替了 Object 模型中的成员方法调用。这样做意义重大,因为相对于成员方法的调用来说,消息的发送是非阻塞的,它无需等待被调用方法执行完成就可以返回
正如上面对Actor模型的介绍一样, dragon中LoginServer就是建立在这个模型上的。需要独立运行并被驱动的对象都用一个独立的线程驱动,这些驱动对象逻辑处理的线程全部从Thread类继承,并且都有自己的消息队列,每个对象之间的通信全部用消息队列的方式.这样的话结构变得非常清晰,自己做自己的事情。自己做完自己负责的事情之后,以消息包的形式通知其他对象接着处理,这可以形象的形容成一个项目组的成员自己做自己的事。做完了告诉其他成员事情做完了,让其他继续负责做下去。一切都井然有序。这样的后果至少能带来最大也最明显的好处:不混乱,数据走向和数据存储不会出错。对象要调用另外一个对象的功能,也不用通过指针来直接调用,减低了对象之间的耦合度,而且保证了多线程运行情况下,不会滥用指针来造成某些不确定因素的情况下程序崩溃 。个人认为,多线程运行的服务器框架下,不同线程间通过指向对象的指针来调用另外一个对象的功能,或者指向对象的同一个指针在不同线程中使用,不但要注意互斥,更重要的是,这样会造成服务器在某些不确定情况下崩溃。既然一个线程用自己的对象来完成自己的任务,则不同线程中的对象之间就不应该发生直接的交互行为,而是应该采用给彼此发送消息的方式发生间接交互.所以,所有对象的逻辑要么都在一个单一的线程中处理,就像游戏逻辑服务器的主线程 一样。要么在多线程服务器框架下使用Actor模型,让消息作为对象和对象之间通信的唯一通道。
言归正传,我们来说说dragon的LoginServer
仔细研究了dragon的LoginServer的代码,感觉他利用Actor模型实现LoginServer在不同状态下的数据转移和处理,
正如开篇说的那样 ,我们从组合LoginServer的核心对象 和 这些对象之间关系的角度出发来研究他。
在主线程的Login::NewLogin()函数中就可以找到这些核心功能对象。
(1)第一个核心对象ProcessManager

从上面的类图可以知道:ProcessManager实际上通过一个单独运行的线程来管理已经验证身份并登录成功的玩家。管理这些玩家的数据发送,数据接受,消息处理。这个对象还管理一个消息队列。其他线程发给消息给此线程中所管理的对象时,都是通过消息包为通信对象,把消息包数据发送到这个队列中。ProcessManager线程就从队列中取出消息并进行处理。能自己处理的ProcessManager就自己处理,不能自己处理的就交给ProcessManager管理的其他对象处理.
(2)第二个核心对象PlayerPool
这个很简单,就是分配登录玩家对象的玩家池,登录玩家是从Player玩家继承而来的,个人认为基类 Player对象是个非常值得借鉴的对象,可重用性很高.封装了很必要的方法,其他类型的玩家可从他继承.
(3)第三个核心对象PacketFactoryManager
Packet对象在Dragon的服务器中运用最为广泛, 我记得以前我们实现的了一个产生任意类型对象的对象池,使用了模板来实现,核心思想就是分配若干个此类型大小的类存块(operator new ),然后用placement new调用对象的构造函数。这里就不详谈,由于这个对象池公司还在用,所以不方便直接贴出源代码。在Dragon中到处都会用到Packet对象,客户端发送数据给服务器,服务器会把数据流转换成包对象的形式存在于服务器端,服务器以Packet对象为基本处理单元进行处理,还有服务器各个线程间对象的数据通信业以Packet对象为交流媒介,所以Dragon就出现了这样上面UML类图展示的生成Packet对象的机制。仔细看上面三个对象PacketFactoryManager,Real_PacketFactory,Real_Packet的耦合度很低,PacketFactoryManager并不知道其他两个对象的存在,只知道其他两个对象的基类。这符合面向对象设计的原则之一:面向接口编程,而不是面向实现编程。这样做的最大好处就是:形成了生成Packet对象的一套逻辑思维上十分清晰的机制,方便程序的扩展和修改。唯一不好的就是:如果产生其他类型的对象池也需要这样 一套机制的话,又要新增许多代码。没有我刚才说的用模板实现对象池来的方便.
(4)TurnPlayerQueue 和WorldPlayerQueue
这两个对象都是FIFO的队列对象。内部实际上是用数组实现的一个先进先出的队列。TurnPlayerQueue类型的对象在LS中是负责管理客户端的连接,如果有客户端连接到来则把要连接到LS的客户端对象放进这个队列中,队列满则排队,TurnPlayerQueue类型的队列大小服务器默认设置的是2048.WorldPlayerQueue类型的队列对象中存储的是玩家从LoingServe到GameServer的排队队列.
(5)第五组核心对象:ThreadManager和ServerManager
从上面的类图可以知道ThreadManager的设计是用来管理线程的,他调用Start函数来创建线程,在这里只是管理了一个线程(ServerThread),这个线程的作用是:管理;LoginServer和其他服务器间的数据通信和消息处理。ServerThread是通过ServerManager对象来完成这i一功能的。在ServerManager中就有两个ServerPlayer对象用来处理到世界服务器和计费服务器之间的消息。在ServerManager对象内部有个死循环一直在调用Select () ProcessCommand()等函数来完成和其他服务器的数据通信和消息处理。而LoginServer的其他线程模块如果要发数据给ServerThread线程模块中的对象时,就是通过ServerManager对象中的SendPacket()来完成的。所以,不难看出,上面的类图展示就是一个LS与其他服务器的通信处理,与本地其他线程模块通信处理的独立模块。他能独立完成自己的功能,而且和其他模块的耦合度通过发送Packet来尽量减小到最低程度。
(6)第六个核心对象:ConnectManager
这个对象从上面的UML类图上可知实际是个线程。而且一直在运行,接收客户端的连接到来,实际的操作交给了LoginPlayerManager对象,从上面的这个类的成员函数可知,其内部也 有个m_PcketQue队列,接收其他线程模块发送给本线程内对象的消息Packet。线程间通信仍然通过消息报的方式进行沟通,让各个线程处理自己的事情,处理完了要让其他线程协助完成接下来的工作时只需要通过Packet告诉他就可以了,这就避免了,在不同线程间内部平凡的直接通过对象或者指向对象的指针调用其他线程模块内对象的成员函数,减小耦合度,减少在多线程环境下出错的几率,而且不在线程间调用彼此的成员函数也避免了大量使用锁的机会。这就像一个团队做一个项目一样,一个程序员把自己的事情做完了,就告诉下个程序员自己的事情做完了,需要他接下来做剩余的工作,只需要告诉他就可以了。这样整个模块的工作就能井然有序的完成。然后再说下这个PlayerManager这个类,这个类有两个子类,一个是LoginPlayerManager一个是ProcessPlayerMananger,当玩家处于连接成功但是未验证成功状态时,玩家对象就被LoginPlayerManager对象管理,如果玩家验证登录成功后,玩家对象就被ProcessPlayerManager管理,这是对玩家处于不同状态时的合理管理方式。本结构是通过继承的方式,通过基类PlayerManager提供的功能进行管理的,当然也可以组合的方式来进行管理。反正都是高耦合哪一种都可以。值得注意的是PlayerManager管理玩家对象是通过玩家的ID来管理的,而不是通过指向玩家对象的指针来管理玩家对象的。这样做有两大好处:第一:减小PlayerManager对象和Player对象的耦合度,第二:只让PlayerPool来维护指向Player对象的指针,而不是在其他很多地方也来引用指向Player对象的指针,要获取指向Player对象的指针只能通过PlayerPool对象来获取。因为越多地方直接引用Player对象的指针,就越难维护。在多线程环境下越容易出错。试想,两个线程引用指针,一个线程删除指针指向的内存,这样在加锁访问指针的情况下,仍然有很小的几率引用空指针。
所以,本人认为多线程下到处引用指向同一对象的指针,是服务器程序不崩溃的潜在危险因素,而且出了问题很难查.所以多线程下,各个线程独立处理自己的任务,各个线程中对象的数据通信全部走消息Packet的方式,这正是现在服务器大量使用的Actor模式.
(7)第七个核心对象 DBThreadManager
这个很简单就不说了,就是创建了N 个DBThread数据库异步IO线程。但是我想他为什么没有组专门的一个数据库服务器,毕竟数据库异步IO处理放在本进程中处理,要开很多线程势必影响本进程效率。
转载时请附上文章原著者地址: http://blog.csdn.net/qq51931373/article/details/29817029