1 Star 0 Fork 0

Eyre / UCAS_OOP_code

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
MaNGOS Zero账号角色管理设计分析.md 15.41 KB
一键复制 编辑 原始数据 按行查看 历史
Eyre 提交于 2021-01-15 20:18 . add some content to report

MaNGOS Zero账号角色管理设计分析

张翔雨 2018K8009929035

项目简介

项目背景

​ MaNGOS(Massive Network Game Object Server Suite)项目是一个开源的自由软件(如同linux或者firefox),并且遵守其中最为严格的GPL协议,也就是保证源代码绝对的自由。其次开发小组一再强调,这是个研究,教育性质的对怎样开发大型网游的服务器端有好处的项目,是一个技术细节毫无保留向公众开放的软件。所以任何利用mangos项目进行私服活动的组织和个人都违反了mangos的宗旨,mangos项目也不会对它们负责。

​ mangos核心部分是个和特定游戏没有关系的框架程序,主要进行进程调度,创造世界,建立心跳机制,处理网络接入等。数据库使用的开源数据库软件MySQL,编译器使用GCC。

下图是mangos团队变迁的过程

img

​ 国内关于mangos的讨论多集中于大芒果论坛:www.mangoscn.com

项目内容

​ 而本文要研究的MaNGOS Zero是其中较为稳定的也是更多人在使用的一个版本。它可以在多个平台上运行,使用MySQL和MariaDB中存储游戏数据。目标是与《魔兽世界》的香草年代(即补丁1.12.1和补丁1.12.2)完全兼容。

​ 香草年代(vanilla version)是指《魔兽世界》的第一个正式版本的时代。那时候的魔兽版本简单功能少,就像纯白的香草冰淇淋。“香草”象征着单纯美好的初恋,所以香草时代的魔兽也是玩家的初恋。后来魔兽世界的更新就像在基础款的香草冰淇淋上加了草莓、巧克力。

文件结构

​ 在src目录下,包含了项目的相关源代码,其中每个文件夹对应实现的功能如下;

  • Game:包括世界系统,战斗系统,游戏事件,游戏场景等的实现
  • Mangosd:mangosd的主程序,包括程序的入口等
  • Modules:目前只有PlayerBots相关实现
  • Realmd:游戏区域信息,包括RealmList等内容
  • Shared:公用函数和库
  • Tools:包含编译的一些脚本

代码结构

​ 两个关键类:

image-20210114114316208

​ 多个关键方法实现:

image-20210114114359112

源码分析

关键类分析

​ 账号角色管理部分的源码主要包括两个类,CharacterHandler和LoginQueryHolder。但其中大部分代码实现的是WorldSession类的方法,因此也做相关说明。

LoginQueryHolder类

image-20210114140040552

​ LoginQueryHolder是继承自SqlQueryHolder的派生类,包含m_accountId和m_guid两个成员变量,只能通过public的方法访问,这里体现了继承、多态、封装的面向对象思想。它的作用是保管登陆时的账号请求信息。

CharacterHandler类

image-20210114155301426

​ CharacterHandler是WorldSession是的友元类,有实例化chrHandler,通过两个方法实现Player对象加载事件处理。

class WorldSession
{
friend class CharacterHandler;
……
}

WorldSession类

WorldSession中实现了很多处理游戏事件的方法,其关键成员如下:

class WorldSession
{
//……
Private:     
        Player* _player;
        WorldSocket* m_Socket;
        ACE_Based::LockedQueue<WorldPacket*, ACE_Thread_Mutex> _recvQueue;
//……

​ 每个session有玩家、信道、消息队列三个部分配合完成事件的处理。当session监听到消息队列中有待处理的事务时,就会调用相关的方法解决,这里面涉及到了ACE反应器模式的事件处理框架,将在后面的部分说明。因此事实上CharacterHandler的作用是是创建Player这个对象,当有了Player这个对象后。

流程分析

​ 以CharacterHandler类的两个方法为例,解释账号角色管理部分的函数实现流程。

角色枚举

void HandleCharEnumCallback(QueryResult* result, uint32 account)
        {
            WorldSession* session = sWorld.FindSession(account);
            if (!session)
            {
                delete result;
                return;
            }
            session->HandleCharEnum(result);
        }

​ 首先先从世界主体获取到要处理的事件对应的session,如果session存在,则调用对应的事件处理函数如下。主要完成的功能为:

  • 生成角色信息数据包,循环查找请求中包含的角色数据信息。
  • 将返回的角色数据信息收集放入数据包发送。
void WorldSession::HandleCharEnum(QueryResult* result)
{
    WorldPacket data(SMSG_CHAR_ENUM, 100);                  // we guess size

    uint8 num = 0;

    data << num;

    if (result)
    {
        do
        {
            uint32 guidlow = (*result)[0].GetUInt32();
            DETAIL_LOG("Build enum data for char guid %u from account %u.", guidlow, GetAccountId());
            if (Player::BuildEnumData(result, &data))
            {
                ++num;
            }
        }
        while (result->NextRow());

        delete result;
    }

    data.put<uint8>(0, num);

    SendPacket(&data);
}

角色登陆

void HandlePlayerLoginCallback(QueryResult * /*dummy*/, SqlQueryHolder* holder)
        {
            if (!holder)
            {
                return;
            }
            WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId());
            if (!session)
            {
                delete holder;
                return;
            }
            session->HandlePlayerLogin((LoginQueryHolder*)holder);
        }

​ 也是和前一个函数相似的实现流程,首先先从世界主体获取到要处理的事件对应的session,如果session存在,则调用对应的事件处理函数在源文件(mangoszero/server/blob/master/src/game/WorldHandlers/CharacterHandler.cpp)589~931行中,由于篇幅过长,这里不直接引用,简要说明其处理流程。

  1. 实例化一个新的Player对象
  2. 从数据库导入账号ID对应的信息,并生成数据包发送
  3. 如果玩家有公会,导入公会信息
  4. 如果是角色第一次登陆,播放CG(send a cinematic)
  5. 进行一些信息的初始化和错误检查
  6. 添加角色至地图,并发送相关消息至服务器端,更新姓名等信息至本地
  7. 初始化社交模块,向好友发送登录信息
  8. 判断角色是否处于死亡、飞行、PVP状态等,并做相应处理
  9. 如果角色是GM角色,则发送相关GM信息

​ 这里体现了面向对象设计原则中的单一职责原则,即所有的操作围绕实例化的Player对象方法完成。

设计模式分析

单例模式

​ MaNGOS Zero在设计时采用了单例模式的设计模式,这里先对单例模式做一个简要的介绍。

简介

​ 单例模式的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

​ 它具有如下三个特点:

​ 1. 单例类只能有一个实例。

		2. 单例类必须自己创建自己的唯一实例。
		3. 单例类必须给所有其他对象提供这一实例。

​ 这些特点主要是为了保证个类仅有一个实例,并提供一个访问它的全局访问点,避免全局使用的类被频繁地创建,控制实例数目,节省了系统资源。

应用场景

​ 这种设计模式在MaNGOS Zero有多次应用,比如世界、拍卖行、野外PK管理等,这里选取和账号角色管理比较相关的世界对象进行说明。世界的定义如下

#define sWorld MaNGOS::Singleton<World>::Instance()

​ 关于单例的类定义在<singleton.h>中,摘取部分定义如下;

T& MaNGOS::Singleton<T, ThreadingModel, CreatePolicy, LifeTimePolicy>::Instance()
    {
        if (!si_instance)
        {
            // double-checked Locking pattern
            Guard();            
            if (!si_instance)
            {
                if (si_destroyed)
                {
                    si_destroyed = false;
                    LifeTimePolicy::OnDeadReference();
                }
                si_instance = CreatePolicy::Create();
                LifeTimePolicy::ScheduleCall(&DestroySingleton);
            }
        }
    	return *si_instance;
    }

​ 要分析单例模式的前提是判断应用单例模式的场景属于单例模式的六种实现方式中的哪一个,其中必须要考虑的问题是如何解决多线程访问带来的隐患,而MaNGOS Zero作为游戏服务器框架多线程访问的问题是不可避免的,因此源码分析需要重点关注这一部分是如何实现的。

​ 从instance函数的代码中可以非常显然地看出,这部分设计者采用的是双重锁验证机制+懒汉模式的方式实现的多线程保护。首先解释懒汉式单例,懒汉式单例指的是单例模式在第一次被引用时,才会将自己实例化的处理方式,这样的处理方式 节省了需要花费的系统资源,但是又面临着多线程访问的问题。考虑以下这种情况:当两个线程都调用instance想要去实例化单例模式的对象,对于instance存在的情况,就直接返回,这不会出现问题。而当instance为 null时,它们将都可以通过第一重 instance为null 的判断。而如果后面没有加锁,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。这时候就需要引入双重锁验证机制,双重锁验证机制相当于给函数加了第二层判断,这两个线程只有一个可以通过guard进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。这样就形成了对多线程访问的保护。

其他设计模式应用

​ 在MaNGOS Zero中还应用到了其他设计模式,由于和分析的模块关系不大,这里只举例不说明,比如生物ai的创建就使用到了工厂模式,数据库查询使用了命令模式。

ACE 反应器模式

简介

​ ACE反应器(ACE Reactor)是用于事件多路分离和分派的体系结构模式,通过ACE_Reactor注册需要监控的事件,当事件发生时,ACE_Reactor就会自动调用注册时指定的控制程序进行处理。MaNGOS Zero中关于世界事件(包括账号角色的操作)的处理就采用了这种方式,具体结构如下:

image-20210114185314456

​ 反应器本质上提供一组更高级的编程抽象,简化了事件驱动的分布式应用的设计和实现。除此而外,反应器还将若干不同种类的事件的多路分离集成到易于使用的API中。特别地,反应器对基于定时器的事件、信号事件、基于I/O端口监控的事件和用户定义的通知进行统一地处理。

​ ACE中的反应器与若干内部和外部组件协同工作。其基本概念是反应器框架检测事件的发生(通过在OS事件多路分离接口上进行侦听),并发出对预登记事件处理器(event handler)对象中的方法的"回调"(callback)。该方法由应用开发者实现,其中含有应用处理此事件的特定代码。例如CharacterHandler类中的两个方法都是对应的回调函数。

​ 开发者要使用反应器时,需要做如下三步:

​ 1.创建ACE_Event_Handler的子类,并在其中实现适当的"handle_"方法,以处理你想要此事件处理器为之服务的事 件类型。

​ 2.通过调用反应器对象的register_handler(),将你的事件处理器登记到反应器。

​ 3.在事件发生时,反应器将自动回调相应的事件处理器对象的适当的handle_"方法。

面向对象思想

​ 1.框架通过继承的动态绑定,采用挂钩方法,解除了“较低级的事件机制” 与“较高级的应用事件处理策略” 的耦合。如此的设计使开发者能在不修改已有应用代码的情况下,对 ACE Reactor 框架进行透明扩展。

​ 2.增加复用并使错误减至最少。ACE Reactor 框架的事件检测、多路分离,以及分派机制是通用的,因而可被许多网络化应用复用。如此的事务分离使得开发者能够专注于高级的、应用所定义的事件处理器策略,而不是反复地与机制进行斗争。

实例

​ 如在HandlePlayerLoginOpcode函数中就在数据库里注册了CharacterHandler::HandlePlayerLoginCallback回调函数,实现了ACE反应器的一个解决方法的实现。

void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recv_data)
{
    ObjectGuid playerGuid;
    recv_data >> playerGuid;
    //……
    CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerLoginCallback, holder);
}

​ 在代码中我也找到了一个例外。ACE反应器模式要求事件处理回调函数实现在为事件处理而建立的子类中,以保证降低上层设计与底层实现之间的耦合,但是HandleCharRenameOpcode和他的回调函数HandleChangePlayerNameOpcodeCallBack都实现在WorldSession类中,并非在对应的Handler类里实现回调函数,这有点令人意外。

开源软件生态问题

在进行调查的过程中发现在一些线上购物网站中有很多售卖魔兽世界单机版的商家。经过调查发现,这些商家大多使用的都是MaNGOS框架。

image-20210115174321238

前文提到过MaNGOS遵循的是最为严格的GPL协议,GPL协议要求确保软件自始至终都以开放源代码形式发布,保护开发成果不被窃取用作商业发售。它是对商业性要求最严格的协议,即要求源代码不能用作任何商业用途。因此在网络上售卖以MaNGOS源码编译的程序是非法的。这也是值得关注的开源软件生态问题。

总结

​ MaNGOS Zero作为在实际应用中实现各种设计模式的项目,对书本上提到的设计模式进行了具体实现,让我对理论知识有了深入理解,也让我深刻了解到面向对象的设计思想在实战中有着非常重要的作用。

参考链接:

[1] https://www.cnblogs.com/lmoonx/articles/9988454.html "MaNGOS魔兽单机学习之路01:MaNGOS大芒果历史背景"

[2] https://www.cnblogs.com/jackcin/p/4121596.html "ACE-Reactor框架"

[3] https://www.cnblogs.com/TianFang/archive/2006/12/13/591332.html "ACE反应器(Reactor)模式"

[4] https://www.cnblogs.com/kongsq/p/9740374.html "23种设计模式(面向对象语言)"

[5] https://www.cnblogs.com/renyuanwei/p/9203088.html "单例模式-双重校验锁"

Java
1
https://gitee.com/zxysbsbzxy/ucas_-oop_code.git
git@gitee.com:zxysbsbzxy/ucas_-oop_code.git
zxysbsbzxy
ucas_-oop_code
UCAS_OOP_code
master

搜索帮助