开源服务器框架NoahFrame分享:第二章 插件与模块
发表于2017-10-17
NF(https://github.com/ketoo/NoahGameFrame)全称为 NoahFrame/NoahGameFrame。
NF最早为客户端设计,后来随着时代的变化,而为自己又转为服务器开发,故在吸收了众多引擎的优点后(包含Ogre的插件模式&模块化管理机制,Bigworld的数据管理&配置机制,类似MYGUI的接口层次设计),经过多年演化和实践,变成了一套游戏开发J解决方案。方案中包含开源的服务器架构,网络库(站在libevent的肩膀上),和unity3d的demo源码。现在NF已经在多个公司的多个项目中使用,其中包含知名产品 《全民无双》。
关键词
NoahGameFrame/NoahFrame/NF
集群/负载均衡/分布式
网关服务器 GateServer 心跳 多线程/线程池 开源网络框架/模型
一致性hash算法/ConsistentHash
游戏开发中的设计模式/数据结构
Socket Nagle/粘包/开源游戏服务器/ Game Server
最初接触插件(Plugin)是当年开发客户端的时候使用的Ogre引擎,里面的设计另当时我这个小菜鸟惊叹不己,原来还可以这样组织代码。然后时隔多年,在Ogre的影响下,又进一步在NF引擎内加入了module和component,用以完善插件式遗漏的一些缺陷。
Ogre的插件式架构,是建立在动态库上的,windows为.dll,linux为.so,NF引擎也是如此(我在主导无双项目在开发的时候,又全部改成了静态库,改成静态库在NF引擎中只需要修改几十行代码即可)。使用插件来组织代码,好处非常多,比如同事分工协作方面,比如逻辑热更新方面(静态语言非脚本,比如使用c 热更新),比如维持代码的单纯度和统一管理规范方面,比如企业安全信息保密方面等等。
NF引擎的插件管理比Ogre略复杂,主要体现在每个插件内部都有module,然后所有的module在启动时又都注册到PluginManager接受PluginManager的管理。NFPluginLoader为程序的执行入口,他会自动查找启动目录下的Plugin.xml文件,然后加载里面配置过的plugin(或者自行传入名字让PluginLoader加载),例如:
Plugin.xml内部声明了每类服务器启动的时候需要加载的插件以及配置文件(NFDataCfg)路径,因为考虑到产品运维更偏向使用脚本批量启动服务器,因此AppID在脚本中可以传入,比如:
插件加载程序的入口在文件NFPluginLoader.cpp的main函数,会先初始化NFCPluginManager,
然后调用NFCPluginManager进行初始化加载动态库(.dll .so),然后统一管理所有的插件,并统一进行初始化,反初始化,帧执行等操作,简略代码如下:
因为任何一个插件(Plugin)必须继承自NFIPlugin类,它拥有NFIModule类所有的统一调用接口(任何模块,必须继承自NFIModule并拥有以下接口Awake, Init, AfterInit, Execute, BeforeShut, Shut, Finalize等统一接口):
这些函数被调用的顺序,和NFCPluginManager启动的时候调用的顺序是一样的,每一个继承NFIModule的类(Mudole),都是按照此顺序运行。
所有的Module类的载体都是插件(动态库),因此为了夸平台,首先NF对要加载的动态库作了一个抽象,用NFCDynLib类来表示(这里向Ogre的作者致敬,几乎纯抄他的),一个动态库就是一个NFCDynLib类对象。同时NFCPluginManager类用来管理所有加载的NFCDynLib类对象,它负责根据动态库文件名对相应的库进行加载,并保存加载后的NFCDynLib对象指针,对于不同平台的插件区分加载,也是这里实现的,代码大概如下:
然后NFCPluginManager::Awake()函数在调用的时候,会先调用LoadPluginConfig()函数来动态库通过查找Plugin.xml内部配置的插件列表来加载需要的插件:
其次会开始执行Init AfterInit等函数,每个函数都会调用内部NFIPlugin的同名接口,而在NFIPlugin内部又会调用所有的Module同名接口。比如Init函数做实例:
调用NFCPluginManager::Init内部会迭代所有的NFIPlugin,来调用NFIPlugin同名Init函数:
//而NFIPlugin内部又回迭代所有的Module,调用Module同名Init函数:
当所有的初始化成功之后,接着就会开始帧循环,以便网络库,心跳库等能够有机会处理自己的逻辑(比如接受消息,然后调用各逻辑模块驱动逻辑进行;又比如到点发奖等内容)代码如下,是不是和Init几乎一样?:
在NF的插件架构中,核心在于module的驱动(因为所有的业务逻辑都在module内),插件plugin只是module的载体。当module实例化后,和插件几乎没什么关系,他们可以跨插件使用其他module而不存在限制,因为NF是面向接口编程,任何module只需依赖其他module的接口(请自行观看各种设计模式书籍)。
那么,如何在一个module中来获取其他module呢?
每个module初始化的时候,都会调用Awake, Init, AfterInit, ReadyExecute 函数,这几个函数一般用来做什么呢,为了大家统一理解,一般作为如下用途:
NFIModule::Awake(): 主要用来初始化自身资源。比如要对其他module提供的服务,比如提供一些随机数池,预生成各种对象等;
NFIModule::Init(): 主要初始化自身需要的一些其他Module的接口,通过pPluginManager->GetModule<NFIModule>()函数来获得其他module的接口,NFIModule请自行替换成你自己写的逻辑类(在NF的世界中,只有的业务逻辑module都建议以NFI 或者 NFC开头,否则在find的时候可能会失败);
NFIModule::AfterInit(): 获取到其他Module接口后,可能依赖其他Module的资源做一些自身的初始化。比如启动网络库开放端口,添加网络MsgID的观察者,添加LogicClass的观察者等;NFIModule::ReadyExecute(): 此函数主要用来在于网络消息处理器的猎夺处理功能,我们希望一些逻辑在新才Module处理,而旧的Module却也不删除,则用可以考虑在此增加代码(参考LoginServer对于登陆消息的猎夺处理)
这里可能有同学比较在意业务动态更新,想使用lua等脚本语言。当然,NF本身是支持lua插件的,但是既然是动态库,插件也可以更新,那么理论上就可以热更新逻辑和配置数据了。
NF是基于Plugin来组织代码,基于Module来提供业务接口,因此可以用GM命令通过NFCPluginManager::ReLoadPlugin(const std::string& strDllName) 重新加载插件。
当插件加载完毕后,会同步通知所有的module,有新插件加载,此时NFIModule::OnReloadPlugin函数,会自动得到回调,使用者可以在OnReloadPlugin重新初始化所有的接口即可,而不用关心其他内容。
NF最早为客户端设计,后来随着时代的变化,而为自己又转为服务器开发,故在吸收了众多引擎的优点后(包含Ogre的插件模式&模块化管理机制,Bigworld的数据管理&配置机制,类似MYGUI的接口层次设计),经过多年演化和实践,变成了一套游戏开发J解决方案。方案中包含开源的服务器架构,网络库(站在libevent的肩膀上),和unity3d的demo源码。现在NF已经在多个公司的多个项目中使用,其中包含知名产品 《全民无双》。
关键词
NoahGameFrame/NoahFrame/NF
集群/负载均衡/分布式
网关服务器 GateServer 心跳 多线程/线程池 开源网络框架/模型
一致性hash算法/ConsistentHash
游戏开发中的设计模式/数据结构
Socket Nagle/粘包/开源游戏服务器/ Game Server
最初接触插件(Plugin)是当年开发客户端的时候使用的Ogre引擎,里面的设计另当时我这个小菜鸟惊叹不己,原来还可以这样组织代码。然后时隔多年,在Ogre的影响下,又进一步在NF引擎内加入了module和component,用以完善插件式遗漏的一些缺陷。
Ogre的插件式架构,是建立在动态库上的,windows为.dll,linux为.so,NF引擎也是如此(我在主导无双项目在开发的时候,又全部改成了静态库,改成静态库在NF引擎中只需要修改几十行代码即可)。使用插件来组织代码,好处非常多,比如同事分工协作方面,比如逻辑热更新方面(静态语言非脚本,比如使用c 热更新),比如维持代码的单纯度和统一管理规范方面,比如企业安全信息保密方面等等。
NF引擎的插件管理比Ogre略复杂,主要体现在每个插件内部都有module,然后所有的module在启动时又都注册到PluginManager接受PluginManager的管理。NFPluginLoader为程序的执行入口,他会自动查找启动目录下的Plugin.xml文件,然后加载里面配置过的plugin(或者自行传入名字让PluginLoader加载),例如:
Plugin.xml内部声明了每类服务器启动的时候需要加载的插件以及配置文件(NFDataCfg)路径,因为考虑到产品运维更偏向使用脚本批量启动服务器,因此AppID在脚本中可以传入,比如:
插件加载程序的入口在文件NFPluginLoader.cpp的main函数,会先初始化NFCPluginManager,
然后调用NFCPluginManager进行初始化加载动态库(.dll .so),然后统一管理所有的插件,并统一进行初始化,反初始化,帧执行等操作,简略代码如下:
因为任何一个插件(Plugin)必须继承自NFIPlugin类,它拥有NFIModule类所有的统一调用接口(任何模块,必须继承自NFIModule并拥有以下接口Awake, Init, AfterInit, Execute, BeforeShut, Shut, Finalize等统一接口):
这些函数被调用的顺序,和NFCPluginManager启动的时候调用的顺序是一样的,每一个继承NFIModule的类(Mudole),都是按照此顺序运行。
所有的Module类的载体都是插件(动态库),因此为了夸平台,首先NF对要加载的动态库作了一个抽象,用NFCDynLib类来表示(这里向Ogre的作者致敬,几乎纯抄他的),一个动态库就是一个NFCDynLib类对象。同时NFCPluginManager类用来管理所有加载的NFCDynLib类对象,它负责根据动态库文件名对相应的库进行加载,并保存加载后的NFCDynLib对象指针,对于不同平台的插件区分加载,也是这里实现的,代码大概如下:
然后NFCPluginManager::Awake()函数在调用的时候,会先调用LoadPluginConfig()函数来动态库通过查找Plugin.xml内部配置的插件列表来加载需要的插件:
其次会开始执行Init AfterInit等函数,每个函数都会调用内部NFIPlugin的同名接口,而在NFIPlugin内部又会调用所有的Module同名接口。比如Init函数做实例:
调用NFCPluginManager::Init内部会迭代所有的NFIPlugin,来调用NFIPlugin同名Init函数:
//而NFIPlugin内部又回迭代所有的Module,调用Module同名Init函数:
当所有的初始化成功之后,接着就会开始帧循环,以便网络库,心跳库等能够有机会处理自己的逻辑(比如接受消息,然后调用各逻辑模块驱动逻辑进行;又比如到点发奖等内容)代码如下,是不是和Init几乎一样?:
在NF的插件架构中,核心在于module的驱动(因为所有的业务逻辑都在module内),插件plugin只是module的载体。当module实例化后,和插件几乎没什么关系,他们可以跨插件使用其他module而不存在限制,因为NF是面向接口编程,任何module只需依赖其他module的接口(请自行观看各种设计模式书籍)。
那么,如何在一个module中来获取其他module呢?
每个module初始化的时候,都会调用Awake, Init, AfterInit, ReadyExecute 函数,这几个函数一般用来做什么呢,为了大家统一理解,一般作为如下用途:
NFIModule::Awake(): 主要用来初始化自身资源。比如要对其他module提供的服务,比如提供一些随机数池,预生成各种对象等;
NFIModule::Init(): 主要初始化自身需要的一些其他Module的接口,通过pPluginManager->GetModule<NFIModule>()函数来获得其他module的接口,NFIModule请自行替换成你自己写的逻辑类(在NF的世界中,只有的业务逻辑module都建议以NFI 或者 NFC开头,否则在find的时候可能会失败);
NFIModule::AfterInit(): 获取到其他Module接口后,可能依赖其他Module的资源做一些自身的初始化。比如启动网络库开放端口,添加网络MsgID的观察者,添加LogicClass的观察者等;NFIModule::ReadyExecute(): 此函数主要用来在于网络消息处理器的猎夺处理功能,我们希望一些逻辑在新才Module处理,而旧的Module却也不删除,则用可以考虑在此增加代码(参考LoginServer对于登陆消息的猎夺处理)
这里可能有同学比较在意业务动态更新,想使用lua等脚本语言。当然,NF本身是支持lua插件的,但是既然是动态库,插件也可以更新,那么理论上就可以热更新逻辑和配置数据了。
NF是基于Plugin来组织代码,基于Module来提供业务接口,因此可以用GM命令通过NFCPluginManager::ReLoadPlugin(const std::string& strDllName) 重新加载插件。
当插件加载完毕后,会同步通知所有的module,有新插件加载,此时NFIModule::OnReloadPlugin函数,会自动得到回调,使用者可以在OnReloadPlugin重新初始化所有的接口即可,而不用关心其他内容。
NF项目为开源的分布式服务器解决方案,其中包含了网络库,actor库,以及数据驱动等新技术,能大幅提升开发效率节省开发周期以及提高程序的稳定性。
如感觉对您有帮助,请给与star,同时也邀请广大同行参与开发和维护,作者QQ 342006,交流QQ群 341159815。
欢迎转载,转载请注明来源,本文版权归作者所有!