c++ 服务器之插件框架开发

服务器开发系列


文章目录

  • 服务器开发系列
  • 前言
  • 一、插件是什么?
  • 二、如何开发插件开发
  • 三、算法插件管理系统
  • 总结


前言


一、插件是什么?

插件的定义
插件(Plug-in,又称addin、add-in、addon或add-on)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下,而不能脱离指定的平台单独运行。
上面的概念可能大家还是云里雾里,介绍个具体的例子,可能大家就明白了插件的概念了,比如vs code,刚安装的时候,vs code时纯净版本来阅读代码或者开发代码时,就是普通的编辑器,但是等你安装c++的部分插件后,开发效率大大提升。如下:
c++ 服务器之插件框架开发_第1张图片

二、如何开发插件开发

一套基本的服务框架,提供多种功能,根据个人的需求来自主选择需要的功能。
1、定义一套标准的插件接口
2、服务框架可以动态的加载、卸载插件
3、实现不同的插件的不同的功能,对外接口是统一的
4、不同插件功能的相互隔离,相互干扰

C++通过动态加载技术即运行时加载,这种方式是通过代码来加载so,具有灵活性。可以通过加载的版本来控制我们加载的so是不是目标so,并且单独更新so和可执行程序时,二者不受影响。
dlopen、dlsym、dlclose都是作加载动态库之用。
dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程,
dlerror返回出现的错误,
dlsym通过句柄和连接符名称获取函数名或者变量名,
dlclose来卸载打开的库

dlopen()

dlopen()函数用来打开一个动态库,并将其加载到进程的地址空间,完成初始化过程,它的原型定义为:

void *dlopen(const char *filename, int flags);
第一个参数是被加载动态库的路径,如果这个路径是绝对路径(以”/”开始的路径),则该函数会将尝试直接打开动态库;如果是相对路径,那么dlopen()会尝试在以一定的顺序查找该动态库文件。一般直接使用绝对路径就可以了。如果filename为NULL,则返回的句柄是main函数的句柄,也就是全局符号表的句柄,也就是说我们可以在运行时找到全局符号表里面的任何一个符号,并且可以执行它们。全局符号表包括了程序的可执行文件本身、被动态链接库加载到进程中的所有共享符号以及在运行时通过dlopen打开并且使用了RTLD_GLOBAL方式的模块中的符号。
第二个参数flag表示函数符号的解析方式,其中RTLD_LAZY和RTLD_NOW必须要设置其中一种。常量RTLD_LAZY表示使用延迟绑定,当函数第一次被用到时才进行绑定,即PLT机制;而RTLD_NOW表示当模块被加载时即完成所有的函数绑定工作,如果有任何未定义的符号引用的绑定工作没法完成,那么dlopen()就返回错误。上面的两种绑定方式必须选其一。

另外还有一些常量可以跟上面的两者中任意一个一起使用(通过常量的“或”操作)。
RTLD_GLOBAL
表示将加载的模块的全局符号合并到进程的全局符号表中,使得以后加载的模块可以使用这些符号。
RTLD_LOCAL
RTLD_LOCAL则与RTLD_GLOBAL相反,使以后加载的模块不能使用这些符号,如果没有定义,默认就是RTLD_LOCAL。
RTLD_NODELETE
在dlclose()期间不要卸载共享对象。因此,如果稍后使用dlopen()重新加载对象,则共享对象的静态变量不会被重新初始化。
RTLD_DEEPBIND
将这个共享对象中符号的查找作用域放在全局作用域之前。这意味着包含对象将优先使用自己的符号,而不是已经加载的其他对象中包含的具有相同名称的全局符号。

dlopen的返回值是被加载的模块的句柄,这个句柄在后面使用dlsym或者dlclose时要用到。如果加载模块失败,则返回NULL。如果模块已经通过dlopen被加载过了,那么返回的是同一个句柄。另外如果被加载的模块之间有依赖关系,比如模块A依赖于模块B,那么程序员必须手工加载被依赖的模块,比如先加载B,再加载A。

dlsym()

dlsym函数就是运行时装载的核心,通过这个函数找到所需要运行的符号,函数原型如下:
void *dlsym(void *handle, const char *symbol); 
第一个参数是有dlopen()返回的动态库的句柄;
第二个参数即所查找的符号的名字,一个以\0结尾的C字符串。如果dlsym找到了相应的符号,则返回该符号的值;没有找到相应的符号,则返回NULLdlsym()返回的值对于不同类型的符号,意义是不同的。如果查找的符号是个函数,那么它返回函数的地址;如果是个变量,它返回变量的地址;如果这个符号是个常量,那么它返回的是该常量的值。
这里有个问题是:如果常量的值刚好是NULL或者0呢,我们如何判断dlsym()是否找到了该符号呢?这就需要用到dlerror()函数了。如果符号找到了,那么dlerror()返回NULL,如果没有找到,dlerror()就会返回相应的错误信息。

也可以通过下面命令来查找函数地址符号是否被编译进入动态库中
nm app.so|grep symbol

dlclose()

dlclose()的作用跟dlopen()刚好相反,它的作用是将上一个已经加载的模块卸载。系统会维持一个加载引用计数器,每次使用dlopen()加载某模块时,相应的计数器加一;每次使用dlclose()卸载某模块时,相应计数器减一。只有当计数器值减到0时,模块才被真正的卸载掉。

dlerror()

每次我们调用dlopen()dlsym()dlclose()以后,都可以调用dlerror()函数来判断上一次调用是否成功。dlerror()的返回值类型是char*,如果返回NULL,则表示上一次调用成功,如果不是,则返回相应的错误消息。

调用插件即so实现代码如下:

#include 
#include 
#include 

#define SO_PATH_ADD "./libadd.so"
#define SO_PATH_SUB "./libsub.so"

typedef int (*func)(int, int);

int main()
{
	{
		void *dlhandler_add;
		func func_add = NULL;
		dlhandler_add = dlopen(SO_PATH_ADD, RTLD_LAZY);
		if (NULL == dlhandler_add){
			fprintf(stderr, "%s\n", dlerror());
			exit(-1);
		}
		dlerror();
		func_add = dlsym(dlhandler_add, "add");
		if (func_add)
			printf("%d\n", func_add(1, 2));
		dlclose(dlhandler_add);
	}
	{
		void *dlhandler_sub;
		func func_sub = NULL;
		dlhandler_sub = dlopen(SO_PATH_SUB, RTLD_LAZY);
		if (NULL == dlhandler_sub){
			fprintf(stderr, "%s\n", dlerror());
			exit(-1);
		}
		dlerror();
		func_sub = dlsym(dlhandler_sub, "sub");
		if (func_sub)
			printf("%d\n", func_sub(1, 2));
		dlclose(dlhandler_sub);
	}
    return 0;
}

三、算法插件管理系统

1、定义插件接口如下:
类中的纯虚函数InitAlg、UninitAlg、AlgVersion、PushData等,需要在插件中实现函数。
类中推送数据函数PushData,将应用层数据推送到插件中处理
类中结果回传回调函数地址 call_back_,当有结果产生时,通过call_back_返回给应用层


#pragma once
#include 
#include 
#include 
#include 

class PluginPushData
{
public:
    PluginPushData(int w,int h,uint8_t* frame) 
    :ih_(h),iw_(w),frame_(frame){
    }
    virtual ~PluginPushData() {
    }
    int ih_;                    
    int iw_;                    
    const uint8_t* Data() const {
        return frame_;
    }
private:                
    uint8_t* frame_;           
};
typedef std::shared_ptr<PluginPushData> PtrPluginPushData;



class IAlgPluginCallback
{
public:
    IAlgPluginCallback() {}
    virtual ~IAlgPluginCallback() {}
    virtual int operator() (std::string name,std::string result) = 0;
};
typedef std::shared_ptr<IAlgPluginCallback> PtrIAlgPluginCallback;

class IAlgPlugin
{
public:
    IAlgPlugin(const std::string& name, PtrIAlgPluginCallback callback)
        : name_(name), call_back_(callback){
    }
    virtual ~IAlgPlugin() {
    }
    const std::string& GetName() const { 
        return name_; 
    }
    
    virtual std::string AlgVersion() = 0;
    virtual int InitAlg(const std::map<std::string, std::string>& conf) = 0;
    virtual void UninitAlg() = 0;
    virtual int PushData(const std::string &camera_id, const PtrPluginPushData data) = 0;    
    PtrIAlgPluginCallback ObtainPluginCallback() {return call_back_;}
protected:
    std::string name_;
    PtrIAlgPluginCallback call_back_;
};
typedef std::shared_ptr<IAlgPlugin> PtrIAlgPlugin;


extern "C" {
    /*
     * 算法模块实现函数,用于获取算法的插件接口类的对象. eg.
     * 
     * class MyAlgPlugin
     * {
     * public:
     *      MyAlgPlugin(const std::string& name, PtrIAlgPluginCallback cb, const std::string& private_data) 
     *          : IAlgPlugin(name,cb), private_attr_(private_data) {}
     *      ~MyAlgPlugin() {}
     *      ....
     * private:
     *      std::string private_attr_;
     * };
     * IAlgPlugin* OnPluginRegistry(const std::string& name, PtrIAlgPluginCallback callback)
     * {
     *      PtrIAlgPlugin plugin(new MyAlgPlugin(name,callback));
     *      return plugin;
     * }
     */

    typedef IAlgPlugin*(*PluginRegistry)(const std::string& name, PtrIAlgPluginCallback);
}


2、A/B/C/…算法插件实现:

根据yaml配置文件进行插件的运行时加载。
plugin为插件列表,支持多个插件同时加载,且不同插件以;隔开,如下同时加载plugin_example_a与plugin_example_b插件
registry_name为插件注册函数列表,一个插件支持多个注册函数,且不同注册函数以;隔开,如plugin_a与plugin_b注册函数
plugin_a,plugin_b为符号表具体函数列表,包括函数名与具体参数列表。

# plugin list, there are multiple plugins, use symbol ';' separate
plugin: plugin_example_a;plugin_example_b;
plugin_example_a:
 load_lib: /home/vagrant/Mycode/pluginManage/plugin/plugin-a/libplugin_a.so
 #registry_name list, there is multiple registry_name, use symbol ';' separate
 registry_name: plugin_a
 plugin_a:
  func: plugin_a_registry
  max_channel: 16
  msgQueueSize: 1000

plugin_example_b:
 load_lib: /home/vagrant/Mycode/pluginManage/plugin/plugin-b/libplugin_b.so
 registry_name: plugin_b
 plugin_b:
  func: plugin_b_registry
  max_channel: 32
  msgQueueSize: 2000

3、插件管理服务实现

以下代码是插件框架中,根据配置文件的具体配置,加载动态库,加载符号列表,卸载动态库的具体实现。
完整代码可以参考: https://download.csdn.net/download/weixin_44834554/86858907

#pragma once

#include "alg_plugin.h"

class PluginLibrary;
class AlgPluginManager
{
public:
    AlgPluginManager();
    ~AlgPluginManager();
    int LoadPluginTable(const std::string& plugin_file);
    int InitPluginTable();
    PtrIAlgPlugin ObtainPlugin(const std::string& plugin_name);
    std::map<std::string, PtrIAlgPlugin>& ObtainPlugins() {
        return plugin_tables_;
    }
    void UninitPluginTable();
    int LoadAPlugin(const std::string& name, const PluginLibrary& plugin_library);
    int PushData2Plugin(const std::string& plugin_func, const std::string &cameraId, const std::string &data);    
private:
    std::map<std::string, PtrIAlgPlugin> plugin_tables_;
};
typedef std::shared_ptr<AlgPluginManager> AlgPluginManagerPtr;

#include 
#include 
#include 
#include "alg_plugin_manager.h"
#include "alg_plugin_config.h"
#include "alg_plugin_rsp.h"

AlgPluginManager::AlgPluginManager()
{
}
AlgPluginManager::~AlgPluginManager()
{
}
int AlgPluginManager::LoadPluginTable(const std::string& plugin_file)
{
    AlgPluginConfig* cfg = AlgPluginConfig::GetInstance();

    int va_ret = cfg->LoadConfig(plugin_file);
    if (va_ret != 0){
        std::cout<< "LoadConfig failed: "<< plugin_file <<std::endl;
        return -1;
    }

    int load_plugin_faild_cnt = 0;
    int load_plugin_ok_cnt = 0;
    const std::map<std::string, PluginLibrary>& plugins = cfg->ObtainPluginLibs();
    
    for (auto itr : plugins){
        va_ret = LoadAPlugin(itr.first, itr.second);
        if (va_ret != 0){
            load_plugin_faild_cnt += 1;
        }
        else{
            load_plugin_ok_cnt += 1;
        }
    }

    return (load_plugin_ok_cnt > 0 ? 0 : -1);
}

int AlgPluginManager::LoadAPlugin(const std::string& name, const PluginLibrary& plugin_library)
{
    std::string plugin_name = plugin_library.library_name_;
    
    std::cout << "------->load plugin '" << name << "', lib: " << plugin_name << " start."<< std::endl;
    // 加载动态库
    void* handle = dlopen(plugin_name.c_str(), RTLD_LAZY|RTLD_LOCAL);
    if (!handle){
        std::cout << "Can'n dlopen share lib '" << plugin_name << ". dlopen err: " << dlerror() << "."<< std::endl;
        return -1;
    }
    
    for (auto reg_itr : plugin_library.registrys_){
        std::map<std::string, std::string>& plugin_param = reg_itr.second;
        if (plugin_param.find("func") == plugin_param.end()){
            continue;
        }
        std::string func_name = plugin_param["func"];
        PluginRegistry func = (PluginRegistry)dlsym(handle, func_name.c_str());
        if (!func){
            std::cout << "Can't dlsym share lib '"<< plugin_name << "' func '"<< func_name << "'." << ". dlsym err: " << dlerror()<< std::endl;
            continue;;
        }

        std::string plugin_instance_name = name + "." + reg_itr.first;
        PtrIAlgPlugin plugin_instance;      
        PtrIAlgPluginCallback plugin_cb(new AlgPluginRsp);
        plugin_instance.reset(func(plugin_instance_name, plugin_cb));            //abstract class to convert to sub class
        
        if (plugin_tables_.find(plugin_instance_name) != plugin_tables_.end()){
            std::cout << "Plugin '" << plugin_name << "' instance '" << plugin_instance_name << "' has exist."
                << "We can't add into plugin tables. "<< std::endl;
            return -1;
        }
        plugin_tables_.insert(std::make_pair(plugin_instance_name, plugin_instance));
        std::cout << "load lib: " << plugin_name << ", load lib symbol: " << func_name << " ,inst: "<< plugin_instance_name<< " success."<< std::endl;
    }
    std::cout << "------->load plugin " << name << "', lib: " << plugin_name <<" end." << std::endl;
    return 0;
}

PtrIAlgPlugin AlgPluginManager::ObtainPlugin(const std::string& plugin_name)
{
    std::map<std::string, PtrIAlgPlugin>::iterator itr = plugin_tables_.find(plugin_name);
    if (itr != plugin_tables_.end()){
        return itr->second;
    }
    PtrIAlgPlugin null_plugin;
    return null_plugin;
}

void AlgPluginManager::UninitPluginTable()
{
    for (auto itr : plugin_tables_){
        itr.second->UninitAlg();
        std::cout<< "unload plugin "<< itr.first <<std::endl;
    }
    plugin_tables_.clear();
}


int AlgPluginManager::PushData2Plugin(const std::string& plugin_func,const std::string &cameraId, const std::string &data)
{
    std::map<std::string, PtrIAlgPlugin>::iterator itr = plugin_tables_.find(plugin_func);
    if (itr == plugin_tables_.end()){
        std::cout<< "not found plugin in plugin_tables_ manager " << plugin_func <<std::endl;
    }
    PtrPluginPushData pushData;
    pushData.reset(new PluginPushData(16,32,(uint8_t*)data.c_str()));
    itr->second->PushData(cameraId,pushData);
    return 0;
}

4、运行如下结果
本代码中,全系列demo,包括插件A与插件B.以及编译插件与插件框架生成可执行程序。
实现了加载plugin_example_a,包括推送数据到插件a,插件a并把数据回调到框架中。
代码实现框架如下:

c++ 服务器之插件框架开发_第2张图片
运行结果如下:
c++ 服务器之插件框架开发_第3张图片


总结

通过本文的讲解,应该对插件开发有了一定的理解,希望对你有所帮助。

你可能感兴趣的:(手把手教你服务器开发,手把手教你C++开发,c++,服务器)