用于管理加载到内存中的共享库的设计模式

时间:2012-10-01 04:15:02

标签: c design-patterns

gcc (GCC) 4.7.2

您好,

我正在开发一个大项目,它将包含我将开发的2个模块(共享库)。

模块是我在C中创建的共享库,它们必须在彼此之间同步和交换消息。

enter image description here

管理器模块将控制这两个模块(.so)并将其加载到内存中。如果一个人失败了。经理可以尝试重新加载它。

我想知道这是我第一次做这样的事情。是否有可以遵循的设计模式?

所有这些都将用C语言编写并使用APR(Apache Portable Runtime)进行内存池管理,如果需要可能还有一些线程池。

  1. 启动管理器,将加载两个模块。
  2. 管理器然后调用它们上的一些函数可能启动和停止它们,并可能进行清理。
  3. 加载并启动两个模块后。他们应该能够在彼此之间交换一些消息。
  4. 模块将在运行Redhat的同一台机器上运行。

    非常感谢任何建议。

7 个答案:

答案 0 :(得分:11)

  

管理器模块将控制这两个模块(.so)并将其加载到内存中。如果一个人失败了。经理可以尝试重新加载它。

如果它在单个C进程中通常是一个坏主意 - 如果其中一个模块发生故障,您不太可能安全地卸载它,更不用说再次加载它。如果您需要能够从模块故障中恢复,则必须使用独立进程。代码仍然可以在.so文件中 - 只需fork()管理器一次加载每个模块;例如,这是chrome plugins API使用的模型。

此外,处理组件故障本身可能非常非常棘手。仅仅因为A重启并不意味着B已经准备好与新重新启动的A进行通信。您可能希望尝试从erlang收集一些想法,通过鼓励将应用程序分解为消息来极好地处理组件故障 - 传递具有管理程序模块层次结构的子组件以重新启动故障组件。如果你只有两个模块,这可能有点矫枉过正,但至少要考虑一下。

至于如何沟通,有很多范例。如果这些模块在同一个过程中,你可以只传递一个vtable。也就是说,例如:

// moduleA.h

struct vtable_A {
  void (*do_something)();
};

void set_vtable_B(struct vtable_B *);
struct vtable_A *get_vtable_A();
void start_A();

// moduleB.h
struct vtable_B {
  void (*do_something)();
};

void set_vtable_A(struct vtable_A *);
struct vtable_B *get_vtable_B();
void start_B();

你的经理会加载两者,将vtable从A传递到B,反之亦然,然后调用启动例程。小心订购 - 要么必须在B准备好之前启动A,要么反之亦然,他们需要对此有所帮助。

如果他们处于独立进程中,则通常需要传递消息。它本质上是一个网络协议 - 您的子进程将序列化消息发送给管理器,管理器将它们路由到其他子进程。谈话可能看起来像这样:

MGR->A      START
MGR->B      START
A->MGR      REGISTER_ENDPOINT 'ProcessA'
A->MGR      WATCH_ENDPOINT 'ProcessB'
MGR->A      OK_REGISTER 'ProcessA'
MGR->A      OK_WATCH 'ProcessB'
B->MGR      REGISTER_ENDPOINT 'ProcessB'
B->MGR      WATCH_ENDPOINT 'ProcessA'
MGR->B      OK_REGISTER 'ProcessB'
MGR->A      NEW_ENDPOINT 'ProcessB'
A->MGR      APPLICATION_DATA TO:'ProcessB', PAYLOAD:"Hello, world!"
MGR->B      OK_WATCH 'ProcessA'
MGR->B      NEW_ENDPOINT 'ProcessA'
MGR->B      APPLICATION_DATA FROM:'ProcessA', PAYLOAD:"Hello, world!"

请记住,除了上面的示例之外,还有许多其他方法可以构建此类协议,并在消息传递协议之上构建RPC。您可能有兴趣查看诸如DBUS(您可以直接使用它们)或DCOM这样的事情,这些事情之前已经完成了这类事情。除此类协议之外的其他优化包括使用管理器在A和B之间建立某种直接通道,并且只有在需要重新启动A或B时再次使用它。

也就是说,在弄清楚需要做什么之前,不要过于深入了解经理的工作细节。设计插件< - > manager 高级接口,以及插件< - >插件协议;然后才设计插件< - >管理器界面的细节。这很容易让人陷入困境,最终变得过于复杂,如CORBASOAP

答案 1 :(得分:5)

在您的请求中基于项目的简单示例之后:

源代码的架构可能是这样的:

src
   |__handler1.c //containing the main function
   |__handler2.c //containing other functions
   |__lib1.c //containing lib1 source
   |__lib2_file1.c  //containing lib2 source
   |__lib2_file2.c  //containing lib2 source
   |__Makefile  // file which contains commands to build the project
   |__inc
         |__lib1.h
         |__lib2.h
         |__handler2.h

<强> handler1.c

#include <stdio.h>
#include "lib1.h"
#include "lib2.h"
#include "handler2.h"

int main()
{
    char *s1, *s2;
    print_hello_from_handler2();
    s1 = get_message_from_lib1_method1();
    get_message_from_lib1_method2(&s2);

    printf("s1 = %s\n",s1);
    printf("s2 = %s\n",s2);
    printf("extern string_from_lib1 = %s\n",string_from_lib1);
    printf("extern string_from_lib2 = %s\n",string_from_lib2);
}

<强> handler2.c

#include <stdio.h>

void print_hello_from_handler2()
{
    printf("hello world from handler2\n");
}

<强> lib1.c

#include "lib2.h"
char *string_from_lib1="message from lib1 variable";

char *get_message_from_lib1_method1()
{
    return get_message_from_lib2_method1();
}

void get_message_from_lib1_method2(char **s)
{
    get_message_from_lib2_method2(s);
}

<强> lib2_file1.c

char *string_from_lib2="message from lib2 variable";

char *str="message from lib2 method1";

char *get_message_from_lib2_method1()
{
    return str;
}

<强> lib2_file2.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void get_message_from_lib2_method2(char **s)
{
    *s = malloc(30);
    strcpy(*s,"message from lib2 method2");
}

<强> lib1.h

extern char *string_from_lib1;

char *get_message_from_lib1_method1();
void get_message_from_lib1_method2(char **s);

<强> lib2.h

extern char *string_from_lib2;

char *get_message_from_lib2_method1();
void get_message_from_lib2_method2(char **s);

<强> handler2.h

void print_hello_from_handler2();

<强>生成文件

SHLIB_EXT=so
LINK=$(CC)
SHLIB1_FILE=libmodule1.$(SHLIB_EXT).1
SHLIB2_FILE=libmodule2.$(SHLIB_EXT).1
SHLIB1_FLAGS=-shared -Wl,-soname,$(SHLIB1_FILE)
SHLIB2_FLAGS=-shared -Wl,-soname,$(SHLIB2_FILE)
FPIC=-fPIC

all: libmodule2.$(SHLIB_EXT) libmodule1.$(SHLIB_EXT) handler


%.o: %.c
    $(CC) -Iinc -c -o $@ $^

handler: handler1.o handler2.o
    $(CC) -o $@ $^ -L. -lmodule2 -lmodule1

lib2_file1.o: lib2_file1.c
    $(CC) $(FPIC) -Iinc -c -o $@ $<

lib2_file2.o: lib2_file2.c
    $(CC) $(FPIC) -Iinc -c -o $@ $<

libmodule2.$(SHLIB_EXT): lib2_file1.o lib2_file2.o
    $(LINK) $(SHLIB2_FLAGS) -o $(SHLIB2_FILE) $^
    ln -sf $(SHLIB2_FILE) $@

libmodule1.o: lib1.c
    $(CC) $(FPIC) -Iinc -c -o $@ $<

libmodule1.$(SHLIB_EXT): libmodule1.o
    $(LINK) $(SHLIB1_FLAGS) -o $(SHLIB1_FILE) $< -L. -lmodule2
    ln -sf $(SHLIB1_FILE) $@


clean:
    rm -f *.o *.so* handler
    rm -f /usr/lib/$(SHLIB1_FILE)
    rm -f /usr/lib/$(SHLIB2_FILE)
    rm -f /usr/lib/libmodule1.$(SHLIB_EXT)
    rm -f /usr/lib/libmodule2.$(SHLIB_EXT)

install:
    cp $(SHLIB1_FILE) /usr/lib/
    cp $(SHLIB2_FILE) /usr/lib/
    cp handler /usr/bin/
    ln -sf /usr/lib/$(SHLIB1_FILE) /usr/lib/libmodule1.$(SHLIB_EXT)
    ln -sf /usr/lib/$(SHLIB2_FILE) /usr/lib/libmodule2.$(SHLIB_EXT)

编译项目的命令

linux$ cd src
linux$ make

然后安装二进制文件和库

linux$ sudo make install

清理已安装的库和二进制文件并清理构建二进制库和对象:

linux$ sudo make clean

运行应用程序:

linux$ handler
hello world from handler2
s1 = message from lib2 method1
s2 = message from lib2 method2
extern string_from_lib1 = message from lib1 variable
extern string_from_lib2 = message from lib2 variable
linux$

答案 2 :(得分:2)

我对“模式谈话”有点过敏,但这就是我接近这个的方法:

  • 决定线程模型。

    • 您的模块是否会使用他们控制的内存或管理员来交换信息?
    • 模块应该等待它们之间共享的条件变量还是经理所拥有的条件变量?
  • 决定您需要的管理员的通用性。

    • 是否可以轮询目录以查找模块或读取配置,还是两者兼而有之?
    • 如果经理管理消息,那么模块之间的信令需要什么?

当你知道这一点时,剩下的应该是主要存在于模块中的业务逻辑。

答案 3 :(得分:1)

当我得到它时,你需要解耦第1点和第2点。

  • 为此你应该有一个名为BootstrapManager的独立类 负责加载模块并在失败时重新加载。
  • 接下来你需要的是一个名为Module的抽象类,它有3个 方法,
       start() - 启动一个模块,    stop() - 停止一个模块,    cleanUp() - 清理活动,    communication() - 与另一个模块通信。
  • 现在,Module1和Module 2都将扩展此类并实现 他们自己的业务逻辑相应。

答案 4 :(得分:1)

如果您已经决定使用APR,您应该使用 dynamic library loading it provides。你可以找到一个教程 here

答案 5 :(得分:1)

这里的架构相对简单,因此您不需要复杂的设计模式。

主要问题是数据完整性。如果系统部分崩溃,您如何确保两者都具有相同的数据副本?

由于您使用的是消息,因此您已经解决了一半问题。你只需要做两件事:

(1)存储最近消息列表并创建回滚/更新机制以恢复给定检查点备份的模块以及自检查点以来的消息列表

(2)确保消息是原子的;即你永远不希望接受部分消息或交易,因为如果发送者在发送消息的过程中崩溃,接收者可能会因接受不完整的信息而被破坏。

要解决问题2,请在事务结束时添加校验和或哈希值。除非接收到散列并匹配数据,否则接收方不会最终确定接受消息集。

答案 6 :(得分:1)

一个关键问题:为什么要以这种方式实现这一点?你“紧密”地耦合了本质上“松散”耦合的组件(因为共享库有各种与崩溃相关的问题:它们会让管理器失效)。为什么不让管理程序(可以)启动并在必要时重新启动2个或更多子进程。

让子进程使用某种协议与Manager或彼此进行通信。我建议ZeroMQ两者都是因为它很棒,因为它完全隐藏了进程间通信,所以它可以是套接字(在不同的机器之间),或者线程之间的进程间处理,或者是单个机器上的命名管道,这是非常快。这意味着在实现客户端之后,您可以决定如何部署它们:作为加载到管理器中的共享库,作为在同一个盒子上运行的独立进程,或作为在不同机器上运行的分布式进程,您几乎不需要改变一切。这意味着非常可扩展。

但是,如果您致力于共享库方法,那么我将绝对推荐一种“设计模式”,尽管实现起来可能有点棘手。但它的价值还是值得的。

在模块之间传递消息之前,您的经理应检查其时间戳,如果有任何更改,请重新编译并重新加载它们。这意味着您的代码更改为“热门”:您不必停止管理器,重新编译并重新启动管理器以查看更改。所以你可以在C中编程更像是在js中开发的!它可以为您节省数小时。

我使用C ++和APR做了类似的事情(不是库内通信)。代码有点'hacky',但无论如何它在这里; - )

请注意,这取决于在其自己的目录中为每个子模块设置Makefile,并且由于依赖性,我不检查时间戳,我只是在每个请求上重新编译。这可能不适合您,因此您可能需要重新考虑该部分。

让它发挥作用的最难的部分是获得对目录的正确权限,但是想到它,那是因为我将它作为fcgi进程运行,所以当它实际运行时它就像webserver一样。你很可能不会遇到这些问题。

#ifndef _CMJ_RUN_HPP
#define _CMJ_RUN_HPP

#include <fcgio.h>
#include <stdlib.h>

#include <iostream>
#include <string>
#include <sstream>
#include <vector>

#include <apr.h>
#include <apr_dso.h>
#include <apr_pools.h>
#include <apr_thread_proc.h>

#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/case_conv.hpp>

#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
#include <stdexcept>

#include <cstdarg>

class Line {
protected:
    std::stringstream line_;
    bool isError_;
public:
    Line(const char* line, bool isError) : line_(line), isError_(isError) {}
    Line(const Line& rhs) : line_(rhs.line()), isError_(rhs.error()) {}
    bool error() const { return isError_; }
    const char* line() const { return line_.str().c_str(); }
    const Line& operator = (const Line& rhs) {
        line_.str() = rhs.line();
        isError_ = rhs.error();
        return rhs;
    }
};

class Run {
protected:
    int exitCode_;
    std::vector<Line> out_;
    bool errors_;
protected:
    void run(const char* dir, const char* cmd, std::vector<const char*> &args, apr_pool_t* parentPool) ;
public:
    Run(const char* dir, const char* cmd, std::vector<const char*> &args, apr_pool_t* parentPool);
    Run(const char* dir, const char* cmd, apr_pool_t* parentPool);
    int exitCode() { return exitCode_; }
    bool errors() { return errors_; }
    bool errors(std::ostream& out);
    int size() { return out_.size(); }
    Line& line(int i) { return out_[i]; }
};

class dso_error: public std::runtime_error {
public:
    dso_error(const char* c) : std::runtime_error(c) {};
    dso_error(std::string err) : std::runtime_error(err) {};
    static dso_error instance(const char* format, ...) {
        char errbuf[8192];
        va_list va;
        va_start(va, format);
        vsnprintf(errbuf, 8192, format, va);
        va_end(va);
        return dso_error(errbuf);
    }
};

/**
 * Provides a building and loading framework for Dynamic libraries, with the full power
 * of make behind it.
 * Usage:
 * <code>
 * DsoLib so("/var/www/frontier/echo","/var/www/frontier/echo/libecho.so",pool);
 * if (!so.errors(outStream)) {
 *  void (*pFn)(void) = sym("initialize");
 *  (*pFn)();
 * }
 * </code>
 */
class DsoLib : public Run {
protected:
    apr_pool_t* pool_;
    apr_dso_handle_t* dso_;
    std::string dirname_;
    std::string libname_;
public:
    /** dir is the directory where make should be executed, libname is full path to the library
     * from current working directory.
     */
    DsoLib(const char* dir, const char* libname, apr_pool_t* parentPool) throw(dso_error);
    ~DsoLib();
    void* sym(const char* symbol) throw (dso_error);
    void* sym(std::string symbol) throw (dso_error) { return sym(symbol.c_str()); }
};

#endif

和Run.cpp

#include "Run.hpp"

#include <string>
#include <sstream>
#include <boost/filesystem.hpp>
#include <cassert>

#define DBGENDL " (" << __FILE__ << ":" << __LINE__ << ")" << endl


using namespace std;

Run::Run(const char* dir, const char* cmd, apr_pool_t* pool) : errors_(false) {
    vector<const char *> args;
    run(dir, cmd, args, pool);
}

Run::Run(const char* dir, const char* cmd, vector<const char*> &args, apr_pool_t* pool) : errors_(false) {
    run(dir, cmd, args, pool);
}

void
Run::run(const char* dir, const char* cmd, vector<const char*> &args, apr_pool_t* parentPool) {
    cout << "Run::run(dir=" << ", cmd=" << cmd << ", args...)" << endl;
    apr_status_t status;
    char aprError[1024];
    struct aprPool_s {
        apr_pool_t* pool_;
        aprPool_s(apr_pool_t* parent) {
            apr_pool_create(&pool_, parent);
        }
        ~aprPool_s() {
            apr_pool_destroy(pool_);
        }
        operator apr_pool_t*  () { return pool_; }
    } pool (parentPool);

    apr_procattr_t* attr;
    if (APR_SUCCESS != (status = apr_procattr_create(&attr, pool))) {
        cerr << "apr_procattr_create error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    if (APR_SUCCESS != (status = apr_procattr_dir_set(attr, dir))) {
        cerr << "apr_procattr_dir_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    if (APR_SUCCESS != (status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_ENV))) {
        cerr << "apr_procattr_cmdtype_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    if (APR_SUCCESS != (status = apr_procattr_io_set(attr, APR_NO_PIPE, APR_FULL_NONBLOCK, APR_FULL_NONBLOCK))) {
        cerr << "apr_procattr_io_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    if (APR_SUCCESS != (status = apr_procattr_user_set(attr, "craig", "lateral"))) {
        cerr << "apr_procattr_user_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    if (APR_SUCCESS != (status = apr_procattr_group_set(attr, "craig"))) {
        cerr << "apr_procattr_group_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    apr_proc_t proc;

    const char **argv = (const char**) new char*[ 2 + args.size() ];
    argv[0] = cmd;
    size_t i=0;
    size_t argc=args.size();
    for (i=0; i<argc; i++) {
        argv[i+1] = args[i];
        cerr << "arg " << i << " = " << args[i];
    }
    argv[i+1] = NULL;
    argc++;
    cerr << "About to execute " << cmd << " in dir " << dir << endl;
    cerr << "ARGS:" << endl;
    for (i=0; i<argc; i++) {
        cerr << "[" << i << "]: " << argv[i] << endl;
    }

    if (APR_SUCCESS != (status = apr_proc_create(&proc, cmd, argv, NULL, attr, pool))) {
        cerr << "apr_proc_create error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }

    apr_exit_why_e exitWhy;
    cerr << "--- " << cmd << " ---" << endl;
    while (APR_CHILD_NOTDONE == (status = apr_proc_wait(&proc, &exitCode_, &exitWhy, APR_NOWAIT))) {
        char line[1024];
        status = apr_file_gets(line, sizeof(line), proc.out);
        if (APR_SUCCESS==status) {
            out_.push_back(Line(line, false));
            cerr << line << endl;
        }

        status = apr_file_gets(line, sizeof(line), proc.err);
        if (APR_SUCCESS==status) {
            out_.push_back(Line(line, true));
            errors_ = true;
            cerr << "E:" << line ;
        }
    }
    cerr << " -----" << endl;

    delete[] argv;

    if ( (APR_CHILD_DONE != status) && (APR_PROC_EXIT != status) ) {
        cerr << "apr_proc_wait error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl;
    }
    cerr << cmd << " exited " << ((APR_PROC_EXIT==exitWhy) ? "PROC_EXIT" :
            ((APR_PROC_SIGNAL==exitWhy) ? "PROC_SIGNAL" :
            ((APR_PROC_SIGNAL_CORE==exitWhy) ? "PROC_SIGNAL_CORE" : "Unknown"))) << endl;
}

bool
Run::errors(std::ostream& os) {
    cerr << "Run::errors(ostream) : errors()=" << errors() << endl;
    if (errors()) {
        cerr << "Writing errors to ostream" << endl;
        os << "Content-type: text/html\r\n\r\n";
        os << "<html><head><title>Errors</title>"
            << "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/frontier.css\"></link>"
            << "</head>"
            << "<body>";
        for (int i=0; i<size(); i++) {
            Line& errline = line(i);
            os << "<div class=\"" << ( (errline.error() ? "error" : "out" ) ) << "\">"
                    << errline.line()
                    << "</div>";
        }
        os
            << "</body>"
            << "</html>";
    }
    return errors();
}

DsoLib::DsoLib(const char* dir, const char* libname, apr_pool_t* parentPool) throw (dso_error) :
    Run(dir, "/usr/bin/make", parentPool), pool_(NULL), dso_(NULL), dirname_(dir), libname_(libname)
{
    if (errors()) {
        cerr << "Run encountered errors, quitting DsoLib::DsoLib()" << DBGENDL;
        //throw dso_error::instance("Build failed for dir %s, library %s", dir, libname);
        return;
    } else {
        cerr << "No errors encountered with Run in DsoLib::DsoLib" << DBGENDL;
    }

    apr_status_t status;
    if (APR_SUCCESS != apr_pool_create(&pool_, parentPool)) {
        cerr << "Failed to allocate pool" << DBGENDL;
        throw dso_error("Failed to allocate apr_pool");
    }

    cerr << "Created pool ok" << DBGENDL;   //(" << __FILE__ << ":" << __LINE__ << ")" << endl;

    if (APR_SUCCESS != (status = apr_dso_load(&dso_, libname, pool_))) {
        cerr << "apr_dso_load(" << libname << ") failed" << DBGENDL;
        char aprError[1024];
        throw dso_error::instance("dso_load failed, path=%s, error=%s",
                libname, apr_strerror(status, aprError, sizeof(aprError)));
    }
    cerr << "Loaded dso ok" << DBGENDL;
#if 0
    void (*initialize)(apr_pool_t*) = reinterpret_cast< void(*)(apr_pool_t*) > (sym("initialize"));
    if (initialize) {
        cerr << "found initialize sym: about to call initialize" << DBGENDL;
        initialize(pool_);
        cerr << "initialize(pool) returned ok" << DBGENDL;
    } else {
        cerr << "initialize sym not found" << DBGENDL;
    }
#endif
    cerr << "Exiting DsoLib::DsoLib(" << dir << ", " << libname << ") with success." << endl;
}

DsoLib::~DsoLib() {
    cerr << "Entering DsoLib::~DsoLib(dir=" << dirname_ <<", " << "lib=" << libname_ << ")" << endl;
    if (NULL!=dso_) {
        void (*terminate)(void) = reinterpret_cast<void(*)()>(sym("terminate"));
        if (terminate) terminate();
        apr_status_t status = apr_dso_unload(dso_);
        if (APR_SUCCESS != status) {
            char err[8192];
            cerr << "ERR apr_dso_unload failed: " << apr_dso_error(dso_, err, sizeof(err)) << endl;
        } else {
            cerr << "Unloaded " << libname_ << endl;
        }
    } else {
        cerr << "ERR dso_ handle is NULL" << endl;
    }
    if (NULL!=pool_) apr_pool_destroy(pool_);
}

void *
DsoLib::sym(const char* symbol) throw (dso_error) {
    cerr << "sym('" << symbol << "')" << DBGENDL;
    cerr << "dso_ == NULL ? " << ((NULL==dso_)?"true":"false") << DBGENDL;
    cerr << "dso_ = " << dso_ << DBGENDL;
    assert(NULL!=symbol);
    assert(NULL!=dso_);
    apr_status_t status;
    void* p = NULL;
    if (APR_SUCCESS != (status = apr_dso_sym((apr_dso_handle_sym_t*)&p, dso_, symbol))) {
        cerr << "apr_dso_sym() DID NOT RETURN APR_SUCCESS" << DBGENDL;
        char aprError[1024];
        stringstream err;
        err << "dso_sym failed, symbol=" << symbol << ": " << apr_strerror(status, aprError, sizeof(aprError));
        cerr << err.str() << DBGENDL;
    } else {
        cerr << "sym succeeded for " << symbol << " in " << libname_ << DBGENDL;
    }
    return p;
}