插件化开发Yate【1】 - 框架介绍

2023-10-20 00:40
文章标签 介绍 开发 框架 插件 yate

本文主要是介绍插件化开发Yate【1】 - 框架介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Yate简介

yate是一个可扩展性的电话引擎,采用观察者(发布-订阅)模式实现消息总线,并通过插件方式对功能模板进行管理,是一个非常灵活有效的应用架构。

Yate的架构如图1所示(借用的图像,如侵权请告知)

图1  Yate框架组成

yate设计为三大部分

1)引擎 ( Modules )

引擎部分实现应用初始化、插件管理、消息总线的基础功能,提供一个非常简洁的应用框架; 通过配置文件将插件组装为应用,系统的功能由不同的模块组合实现,在扩展所需功能与性能、稳定性之间达到最佳平衡。

引擎主要包括基础库,配置管理、插件管理,以及消息运行线程池、消息分发管理和消息队列管理等。

(2) 模块( Modules )

应用的业务功能主要由运行时加载的模块来实现,这些模块以动态链接库为载体,作为插件被引擎或外部程序的特定模块加载。插件之间通过消息总线、数据通道等形式完成不同模块之间的交互,实现完整的业务逻辑。

插件提供动态加载、按需加载的能力,并可根据需要,卸载或者动态更新插件,实现插件的热插拔特性。

插件可以根据需要,重新进行配置; 可以进行系统配置更新,也可以单独对某个模块独立进行配置更新;

(3) 消息( Messages )

yate 模块(包括插件和外部模块)之间的交互通过消息的形式,实现的不同模块之间的解耦。 消息可分为有响应的消息、无响应消息等多种形式。 消息提供了一种可扩展,可定制的交互机制。

框架引擎Engine类

Yate 以Engine类为核心,构建了插件式的管理体系。Engine类的重要函数、属性为

class YATE_API Engine
{/*** Main entry point to be called directly from a wrapper program* @param argc Argument count* @param argv Argument array* @param env Environment variables* @param mode Mode the engine must run as - Console, Client or Server* @param loop Callback function to the main thread's loop* @param fail Fail and return after parsing command line arguments* @return Program exit code*/static int main(int argc, const char** argv, const char** env,RunMode mode = Console, EngineLoop loop = 0, bool fail = false);/*** Register or unregister a plugin to the engine.* @param plugin A pointer to the plugin to (un)register* @param reg True to register (default), false to unregister* @return True on success, false on failure*/static bool Register(const Plugin* plugin, bool reg = true);/*** Installs a handler in the dispatcher.* @param handler A pointer to the handler to install* @return True on success, false on failure*/static bool install(MessageHandler* handler);/*** Uninstalls a handler drom the dispatcher.* @param handler A pointer to the handler to uninstall* @return True on success, false on failure*/static bool uninstall(MessageHandler* handler);/*** Enqueue a message in the message queue for asynchronous dispatching* @param msg The message to enqueue, will be destroyed after dispatching* @param skipHooks True to append the message directly into the main queue* @return True if enqueued, false on error (already queued)*/static bool enqueue(Message* msg, bool skipHooks = false);/*** Synchronously dispatch a message to the registered handlers* @param msg The message to dispatch* @return True if one handler accepted it, false if all ignored*/static bool dispatch(Message& msg);/*** Loads one plugin from a shared object file* @param file Name of the plugin file to load* @param local Attempt to keep symbols local if supported by the system* @param nounload Never unload the module from memory, finalize if possible* @return True if success, false on failure*/bool loadPlugin(const char* file, bool local = false, bool nounload = false);/*** Loads the plugins from the plugins directory*/void loadPlugins();/*** Initialize all registered plugins*/void initPlugins();private:Engine();ObjList m_libs;   // 插件列表MessageDispatcher m_dispatcher;  // 消息分发器uint64_t m_dispatchedLast;unsigned int m_messageRate;unsigned int m_maxMsgRate;bool m_rateCongested;bool m_queueCongested;bool m_ageCongested;static Engine* s_self;static String s_node;static String s_shrpath;static String s_cfgsuffix;static String s_modpath;static String s_modsuffix;static ObjList s_extramod;static NamedList s_params;static int s_haltcode;static RunMode s_mode;static bool s_started;static unsigned int s_congestion;}

Engine::main为引擎的入口函数,完成参数解析、配置文件读取、初始化消息分发器、加载插件、初始化插件,然后进入循环,在循环处理中,检测退出信息,发送定时消息engine.timer等处理流程。

通过—help参数,可以了解yate的启动参数

Usage: yate [options] [commands ...]-h, --help     Display help message (this one) and exit-V, --version  Display program version and exit-v             Verbose logging (you can use more than once)-q             Quieter logging (you can use more than once)--service      Run as Windows service--install      Install the Windows service--remove       Remove the Windows service-p filename    Write PID to file-l filename    Log to file-n configname  Use specified configuration name (yate)-e pathname    Path to shared files directory (.\share)-c pathname    Path to conf files directory (.\conf.d)-u pathname    Path to user files directory (C:\Users\wald\AppData\Roaming\Yate)-m pathname    Path to modules directory (.\modules)-x dirpath     Absolute or relative path to extra modules directory (can be repeated)-w directory   Change working directory-N nodename    Set the name of this node in a cluster-A cpus        Set affinity from comma separated list of CPUs (e.g 1-4,7,8)-t             Truncate log file, don't append to it-D[options]    Special debugging optionsa            Abort if bugs are encounteredm            Attempt to debug mutex deadlocksd            Enable locking debugging and safety featuresc            Call dlclose() until it gets an erroru            Do not unload modules on exit, just finalizei            Reinitialize after 1st initializationx            Exit immediately after initializationw            Delay creation of 1st worker threado            Colorize output using ANSI codess            Abort on bugs even during shutdownO            Attempt to debug object allocationsn            Do not timestamp debugging messagest            Timestamp debugging messages relative to program starte            Timestamp debugging messages based on EPOCH (1-1-1970 GMT)f            Timestamp debugging in GMT format YYYYMMDDhhmmss.uuuuuuF            Timestamp debugging in GMT format YYYY-MM-DD_hh:mm:ss.uuuuuuz            Timestamp debugging in local timezone YYYYMMDDhhmmss.uuuuuuZ            Timestamp debugging in local timezone YYYY-MM-DD_hh:mm:ss.uuuuuu


引擎通过m_libs成员实现对插件进行管理,引擎在加载动态库的时候,会对插件对象进行实例化,通过插件对象的构造函数,调用Register(const Plugin* plugin, bool reg = true)接口,完成插件的注册工作,在卸载动态库的时候,插件对象析构函数会调用Register(const Plugin* plugin, false)接口,完成插件的卸载动作。

Engine类提供了一些 API (静态函数)用于加载特定模块,和指定目录下的所有模块。

// 加载指定目录下模块参数 relPath 相对主模块的路径

bool loadPluginDir(const String& relPath);

// 注册插件,只有注册过的插件才能被初始化

bool Register(const Plugin* plugin, bool reg = true);

// 加载指定模块

bool loadPlugin(const char* file, bool local,bool nounload); private

void loadPlugins();// 从插件目录中加载插件   private

void initPlugins();// 初始化插件   private

引擎通过MessageDispatcher类实现消息总线的管理功能,主要管理消息订阅者列表和消息队列

class YATE_API MessageDispatcher : public GenObject, public Mutex
{
......
private:ObjList m_handlers;   // 订阅者列表ObjList m_messages;  // 消息队列
}

每个订阅者都实现了MessageHandler接口用于接收通知。

class MessageHandler
{virtual bool received(Message& msg) = 0;
}

类 Engine 还提供了一系列的 API (静态成员函数),用于订阅消息和发送消息。

// 安装注册订阅者的接口,参数指定订阅的事件类型

static bool install(MessageHandler* handler);   // 订阅消息

static bool uninstall(MessageHandler* handler);  // 取消订阅

// 发送事件通知,所有注册了该事件类型的都有机会得到事件内容

//enqueue 为非阻塞函数,即将事件通知加入消息列表中,不关系事件处理的结果

//dispatch 为阻塞函数,即必须等到订阅者处理完事件才能返回。

static bool enqueue(Message* msg);

static bool dispatch(Message* msg);

为方便实现消息处理,通过YateMsgHandler类实现了消息处理接口,通过std::function< bool(TelEngine::Message& msg)> _handler管理的函数实现功能,简化程序实现。

class YateMsgHandler : public TelEngine::MessageHandler
{
public:YateMsgHandler(const char* name, unsigned priority = 100,const char* trackName = 0, bool addPriority = true) :TelEngine::MessageHandler(name, priority, trackName, addPriority) {}virtual bool received(TelEngine::Message& msg) {if (_handler) {return _handler(msg);}return false;}
public:std::function< bool(TelEngine::Message& msg)> _handler;
};

插件Plugin类

插件的基类 Plugin 类,需要实现 initialize() 这个纯虚函数。Plugin类定义如下

/*** Initialization and information about plugins.* Plugins are located in @em shared libraries that are loaded at runtime.**<pre>* // Create static Plugin object by using the provided macro* INIT_PLUGIN(Plugin);*</pre>* @short Plugin support*/
class YATE_API Plugin : public GenObject, public DebugEnabler
{
public:/*** Creates a new Plugin container.* @param name the undecorated name of the library that contains the plugin* @param earlyInit True to initialize the plugin early*/explicit Plugin(const char* name, bool earlyInit = false);/*** Destroys the plugin.* The destructor must never be called directly - the Loader will do it*  when the shared object's reference count reaches zero.*/virtual ~Plugin();/*** Get a string representation of this object* @return Name of the plugin*/virtual const String& toString() const{ return m_name; }/*** Get a pointer to a derived class given that class name* @param name Name of the class we are asking for* @return Pointer to the requested class or NULL if this object doesn't implement it*/virtual void* getObject(const String& name) const;/*** Initialize the plugin after it was loaded and registered.*/virtual void initialize() = 0;/*** Check if the module is actively used.* @return True if the plugin is in use, false if should be ok to restart*/virtual bool isBusy() const{ return false; }/*** Retrieve the name of the plugin* @return The plugin's name as String*/inline const String& name() const{ return m_name; }/*** Retrive the objects counter associated to this plugin* @return Pointer to plugin's objects counter or NULL*/inline NamedCounter* objectsCounter() const{ return m_counter; }/*** Check if the module is to be initialized early* @return True if the module should be initialized before regular ones*/bool earlyInit() const{ return m_early; }private:Plugin(); // no default constructor pleaseString m_name;NamedCounter* m_counter;bool m_early;
};

插件必须从 Plugin 类派生,并实现 initialize() 这个虚函数,另外,根据需要实现 Message 的处理类 MessageHandler。在 initialize() 里,按照主题安装 MessageHandler ,如下所示:

Engine::install(new AuthHandler(s_cfg.getIntValue("general","auth_priority",70)));

上例的 AuthHandler 即是从 MessageHandler 中派生而来,它必须实现 received() 虚函数,以处理接收到的 message。

插件通常采用静态变量的方式实现,即在一个动态库中,通过宏INIT_PLUGIN实现,宏定义如下

#define INIT_PLUGIN(pclass) static pclass __plugin

引擎在加载动态库的时候,会对插件对象进行实例化,通过插件对象的构造函数,调用Register(const Plugin* plugin, bool reg = true)接口,完成插件的注册工作,在卸载动态库的时候,插件对象析构函数会调用Register(const Plugin* plugin, false)接口,完成插件的卸载动作。

Plugin::Plugin(const char* name, bool earlyInit): m_name(name), m_early(earlyInit)
{Debug(DebugAll,"Plugin::Plugin(\"%s\",%s) [%p]",name,String::boolText(earlyInit),this);debugName(m_name);m_counter = getObjCounter(m_name);Engine::Register(this);
}Plugin::~Plugin()
{Debugger debug("Plugin::~Plugin()"," \"%s\" [%p]",m_name.c_str(),this);Engine::Register(this,false);debugName(0);
}

Yate消息

在 Yate 中,消息取代函数成为模块间主要的交互方式。这样的好处在于,当一个模块改变时,其他独立的模块不用做任何修改。另外,因为我们能够轻松的跟踪到消息的传递过程,所以调试起来相对容易。

消息包括以下几个组成部分:

(1) 名字( name ) —— 消息类型的标识,允许消息处理器通过名字进行匹配

(2) 返回值( return value ) —— 一个用字符串表示的处理消息之后的返回值

(3) 时间( time ) —— 消息被创建的时间;这对于排队的消息检查等待时长非常重要

(4) 参数( parameters ) —— 可能有多个或 0 个参数组成,每个参数由名称、值对构成。每个处理器都能根据参数进行不同的动作,或者修改参数本身。未定义参数必须忽略。

(5) 用户数据( userData )—— RefObject对象指针数据,通过该指针,可以封装任意类型的数据,可以在消息中携带更丰富的信息。

(6) 消息类型 —— 是否为广播类型,非广播类型可以通过消息处理器控制实现停止处理此消息(释放),或让此消息滑动到下一个订阅者。广播类型消息将发送到每个消息订阅者中进行处理。

所有的消息在 YATE 内部是二进制形式的。然而我们可以通过 rmanager 模块提供一个对人可读的形式。

    YATE 内部消息传递通过内存共享( memory sharing )的方式,提高系统的性能。其他传递方式如管道或 Sockets ,没有内存共享灵活和高效。当被传递到外部模块( external modules )时,消息可被转换成字符串编码的形式,这样所有能处理文本的外部模块都可以处理消息了。可参考文档 external module ,获取更多详细信息。

消息被消息处理器( MessageHandler )处理。消息处理器接收名字匹配的消息,可以对其中的组成部分进行修改,可停止处理此消息(释放),或让此消息滑动到下一个操作者。

消息处理器接收消息分发器通知的顺序在其向引擎注册时提供的优先级决定。优先级数字越小,优先级越高。对于相同优先级的消息处理器,调用顺序是不确定的。

调用顺序按以下的规则:

  * 同名的消息调用顺序是不会改变的
  * 为了避免不确定性,如果消息处理器被移除,并插入一个同等优先级的消息处理器,则他们的顺序由她的的内存地址决定。

为了方便userData的使用,扩展实现了模板对象AnyRefObject,通过Any对象,可以封装任意的指针。

template<class T>
class AnyRefObject : public TelEngine::RefObject {enum anytype {any_shared_ptr,any_native_ptr};public:virtual void* getObject(const TelEngine::String& name) const {if (name == ANY_REF_OBJECT_TYPE_NAME) {return (void*)this;}return RefObject::getObject(name);}const char* getTemplateName() {return typeid(_my_template).name();}const TelEngine::String& getSaveTemplateName() {return _save_template_name;}bool isCastValidate() {return _save_template_name == getTemplateName();}void set(T* t, bool withname = true) {_anytype = any_native_ptr;_any = TelEngine::Any(t);if (withname) {_save_template_name = getTemplateName();}_with_name = withname;}void set(const std::shared_ptr<T>& t, bool withname = true) {_anytype = any_shared_ptr;_any = TelEngine::Any(t);if (withname) {_save_template_name = getTemplateName();}_with_name = withname;}T* get_native() {if (_anytype != anytype::any_native_ptr) {return nullptr;}return TelEngine::any_cast<T*>(_any);}std::shared_ptr<T> get_shared() {if (_anytype != anytype::any_shared_ptr) {return nullptr;}return _any.Get< std::shared_ptr<T> >();}bool _with_name = false;T* _my_template = 0;protected:anytype _anytype;TelEngine::Any _any;// 封装的模板类名TelEngine::String _save_template_name;};template<class T>
T* AnyRefObject_np(const TelEngine::Message & msg) {TelEngine::RefObject* ref_object = msg.userData();if (!ref_object) {return nullptr;}void* userData = ref_object->getObject(ANY_REF_OBJECT_TYPE_NAME);if (!userData) {return nullptr;}AnyRefObject<T>* user_data = (AnyRefObject<T> *)userData;if (!user_data->isCastValidate()) {return nullptr;}return user_data->get_native();
}template<class T>
std::shared_ptr<T> AnyRefObject_sp(const TelEngine::Message & msg) {TelEngine::RefObject* ref_object = msg.userData();if (!ref_object) {return nullptr;}void* userData = ref_object->getObject(ANY_REF_OBJECT_TYPE_NAME);if (!userData) {return nullptr;}AnyRefObject<T>* user_data = (AnyRefObject<T> *)userData;if (!user_data->isCastValidate()) {return nullptr;}return user_data->get_shared();
}

引擎消息:

      engine.start 由引擎发送,通知他们 Yate 准备就绪,并已进入主循环

     engine.stop 由引擎发送,通知他们 Yate 准备退出主循环

      engine.halt 由引擎发送,通知引擎已经停止

      engine.init

      engine.busy

      engine.help

      engine.command

      engine.status

      engine.timer 由引擎发送,每秒触发一次

【上一篇】插件化开发Yate【0】 - 准备-CSDN博客

【系列】插件化开发Yate【2】 - 简单插件Demo-CSDN博客

【系列】插件化开发Yate【3】 - 热插拔插件Demo_iseelgy的博客-CSDN博客

计划内容

本系列计划包括多项内容,主要包括

1. Yate整合libevent、evpp实现插件化事件框架

2.Yate整合Rxcpp,实现响应式插件框架

3.Yate整合Qt,实现Qt组件的插件化开发

4.Yate在Linux、Windows、Android、鸿蒙等平台实现插件化开发

参考资料

【1】yate开源库地址 https://github.com/yatevoip/yate

【2】Yate架构分析概要_阿利518的博客-CSDN博客

【3】yate 指南_iteye_3055的博客-CSDN博客

【4】yate开发向导_一枪尽骚丶魂的博客-CSDN博客

下载

提供原始Yate客户端和源码下载,下载地址为:

https://download.csdn.net/download/iseelgy/88425272

这篇关于插件化开发Yate【1】 - 框架介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/243468

相关文章

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Pytest多环境切换的常见方法介绍

《Pytest多环境切换的常见方法介绍》Pytest作为自动化测试的主力框架,如何实现本地、测试、预发、生产环境的灵活切换,本文总结了通过pytest框架实现自由环境切换的几种方法,大家可以根据需要进... 目录1.pytest-base-url2.hooks函数3.yml和fixture结论你是否也遇到过

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应