关注微信公众号“池边小树”~~获取更多分析~~(文末二维码~~)
目录
4.1 Component Configurator架构模式
4.10.3 ACE_Service_Object_Type类
4.10.4 ACE_Service_Type_Factory类
4 Service Configurator框架
Service ConfigUR按投入框架(简称Configurator框架),该框架使用Component Configurator框架设计模式。这种架构模式通过解耦服务的行为和服务配置到应用程序中的时间点,提高应用程序的可扩展性和灵活性。通过Configurator框架,应用程序可以延缓配置和实现一直到程序的运行期,而不是在编译期。
ACE的Configurator框架实现了两种不同的配置服务的方式:一种是静态地配置到应用程序中,另一种是通过一个或多个动态库(DLL)配置到应用程序中。
4.1 Component Configurator架构模式
(1)意图
Component Configurator架构模式通过动态链接库使应用程序可以在运行期实现组件配置。采用Component Configurator架构模式,应用程序无须重新编译、重新启动,便可实现对配置的修改,从而提高应用程序的可配置性和模块的可重用性。
(2)上下文
系统中的某个组件需要灵活地支持初始化、挂起、恢复和终止操作。
(3)问题
由多个组件组成的应用程序需要通过一种机制来配置这些组件,这种机制受以下几方面的影响。
- ①在许多应用程序中,改变组件的功能和实现细节是十分普遍的,例如发现了新的算法、新的构架等。同时,在改变某个组件时还要求尽可能不影响其他组件。
- ②在应用程序的开发阶段,开发人员并不知道组件的最佳配置方法。
- ③对组件进行的配置、初始化和控制应该和具体的组件无关。
- ④将组件的接口和实现分离开,使应用程序独立于这些组件,组件通过配置的方式进入应用程序。
(4)解决方案
将组件接口和具体的组件解耦,同时将应用程序和组件配置到程序的时间点分离。
(5)结构
Component Configurator构架模式有以下几个关键的参与者:
- ①组件(Component):一个统一的接口,用来配置和控制应用程序提供的服务类型和组件实现的功能。常用的控制操作包括initializing(初始化)、suspending(挂起)、resuming(恢复)和terminating(终止)。
- ②具体组件:实现组件接口,提供一种特定的组件类型。具体组件通过实现接口中的模板函数来提供应用程序的功能,它们以DLL的实现在程序的运行期被添加到应用程序中或从应用程序中删除。
- ③组件库:用来管理所有被添加到应用程序中的具体组件。组件配置器可以使用组件库来添加、查找和删除组件。
- ④组件配置器:使用组件库来调整组件的配置。它实现了通过一种机制来解析和执行配置脚本,这些配置脚本详细描述了那些可以通过DLL的方式被动态配置到应用程序中的具体组件。
这些参与者之间的结构关系如图4-1所示。
图4-1 Component Configurator构架模式的结构图
4.2 Configurator框架概述
动态库是通过虚拟内存管理机制在运行期被一次性装载到物理内存中,被操作系统的多个进程同时使用的库。Windows将动态库称为动态链接库,其文件扩展名为.dll,Linux称其为共享库,扩展名为.so。
使用动态库有两种不同的方式:一种是隐式链接,由编译系统根据动态库的头文件和库文件进行编译和链接,从而确定待调用的函数原形和地址;一种是显示链接,利用动态库接口动态地实现库的加载和卸载、获取调用函数地址、获取错误信息等。Configurator框架使用显示链接技术实现配置组件的动态加载和卸载。
4.3 Configurator框架应用示例1
这个示例由3部分组成:一个配置文件svc.conf、一个主文件dynamic.cpp和一个可动态修改的组件(由this_dll.cpp和that_dll.cpp组成)。
4.3.1 配置文件
如下给出了一个配置文件,它是一个配置脚本,内容和格式是Configurator框架规定的,文件名称是svc.conf,这个是Configurator框架默认的配置文件名。
配置文件采用的是ACE规定的一种格式。ACE规定了两种不同的文件格式:一种是示例1中的格式,另一种是XML格式。
- ①第一行是注解,可以使用#添加注解。此处应注意,注解要么占用一行要么在一行的结尾。
- ②dynamic关键字表示采用动态的配置方式(静态配置使用static关键字),DynamicObj是配置组件的名称,Service_Object用于表述配置组件的类型,它是配置文件的关键字。./libthis_dll.so表示当前目录下的libthis_dll.so动态库。_make_DynamicObj()是创建配置组件的工厂函数," "内是应用程序传入的配置参数(本示例中的参数为空),这些参数会传递给配置组件的init函数。
- ③suspend、resume、remove关键字是要对配置组件采取的动作,它们告诉框架调用配置组件对应的suspend、resume和fini函数,我们称这些函数为动作指令。
4.3.2 可配置组件
组件是可配置的,所以组件可以有多个实现。
配置组件是ACE_Service_Object接口的子类。下面给出this_dll.cpp的代码
ACE_Service_Object是Configurator框架提供给应用程序的抽象接口,任何可配置组件都必须是它的子类。
清单中的ACE_FACTORY_DEFINE宏是将组件、框架及配置文件绑定在一起的纽带。
从配置组件的代码中,我们可以产出配置文件中国的组件名称(DynamicObj)和配置组件的工厂函数名称(_make_DynamicObj)的来源,其中组件名称是配置组件的类名,工厂函数名称是在类名之前添加_make_前缀而形成的。
4.3.3 main函数
main函数的代码清单如下:
分两部分:
- 第一部分是使用Reactor框架注册和调度的信号量事件。之所以要使用Reactor框架,一是因为需要Reactor框架来处理配置改变的信号量事件。二是因为在应用程序中还可能有很多I/O事件及其他类型的事件正在交由Reactor框架处理。
- 第二部分是打开Configurator框架。
4.4 ACE动态库接口封装的分析
动态库接口的封装使用了Facade设计模式,是ACE网络框架的一个独立组件,它既可以被框架使用,也可以被应用程序直接使用,称之为DLL组件。
DLL组件主要设计3个类,分别是ACE_DLL类、ACE_DLL_Handle类和ACE_DLL_Manager类,它们之间的关系如图4-2所示。
图4-2 DLL相关的结构图
- ①ACE_DLL_Handle类是动态库接口的原始封装,它的一些方法直接调用了操作系统的动态库接口。应用程序每打开一个动态库都有一个ACE_DLL_Handle对象与之对应,用于保存动态库的名称和打开后的动态库句柄。
- ②为了满足对动态库有效管理的应用要求,ACE设计了ACE_DLL_Manager类,它使用了Singleton设计模式,一个进程只有一个ACE_DLL_Manager类的实例,它是一个仓库,用于保存打开的ACE_DLL_Handle对象。
- ③虽然ACE_DLL_Manager类加上ACE_DLL_Handle类可以满足组件的功能需求,但是这些并不能满足组件本身操作简单的需求。应用程序需要知道ACE_DLL_Manager类的Singleton接口,显然,对于一个小的组件来说,这样做太麻烦。因此,ACE又设计了ACE_DLL_Manager类,它通过Facade设计模式封装了ACE_DLL_Handle类和ACE_DLL_Manager类的操作,让应用程序根本不需要感知它们的存在,简化了DLL组件的使用。
4.5 配置组件接口分析
ACE_Service_Object类是应用程序可配置组件的接口,其结构如图4-3所示。
图4-3 配置组件的接口结构图
ACE_Service_Object类是ACE_Event_Handler 的子类,因此它可以被Reactor框架调度。通过继承关系,也把Reactor框架和Configurator框架有机地结合起来。ACE_Service_Object类的定义如下:
// 代码在ace/Service_Object.h中
class ACE_Export ACE_Service_Object
: public ACE_Event_Handler,
public ACE_Shared_Object
{
public:
// = Initialization and termination methods.初始化函数和清理函数
/// Constructor.构造函数
ACE_Service_Object (ACE_Reactor * = 0);
/// Destructor.析构函数
virtual ~ACE_Service_Object (void);
/// Temporarily disable a service without removing it completely.
virtual int suspend (void);// 挂起服务,在不删除服务的情况下,暂时停止服务
/// Re-enable a previously suspended service.// 恢复先前被挂起的服务
virtual int resume (void);
};
suspend函数对应配置文件的suspend动作,resume函数对应配置文件中的resume动作。
ACE_Shared_Object类是动态库相关操作的接口,代码清单如下。init函数在打开动态库时被调用,可以传入参数,这些参数可以在配置文件中指定。而fini函数则在remove动作中被调用。info函数用于返回程序运行时的动态信息。
// 代码在ace/Share_Object.h中
class ACE_Export ACE_Shared_Object
{
public:
/// Constructor 构造函数
ACE_Shared_Object (void);
/// Destructor 析构函数
virtual ~ACE_Shared_Object (void);
/// Initializes object when dynamic linking occurs. 用于装载动态库时,初始化对象
virtual int init (int argc, ACE_TCHAR *argv[]);
/// Terminates object when dynamic unlinking occurs. 用于卸载动态库时,清理对象
virtual int fini (void);
/// Returns information on a service object. 用于返回对象运行期的信息
virtual int info (ACE_TCHAR **info_string, size_t length = 0) const;
};
既然ACE_Service_Object是Configurator框架的配置组件接口,那么它的模板函数就会被框架调用。在解析配置文件后,框架会根据配置文件的指令调用组件的相关函数。
4.6 组件工厂函数的分析
Configurator框架要执行配置组件的函数,实现配置组件的功能,就必须创建配置组件的实例。但是每一个配置组件都作为一个动态库存在于框架外,框架根本不知道它们的具体类型,也不知道如何创建和销毁它们。因此框架必须提供某种方法,通过这种方法,应用程序可以告诉框架如何来创建和销毁配置组件的实例。ACE使用工厂函数来解决上述问题。每个配置组件都必须调用ACE_FACTORY_DEFINE宏来定义与其对应的工厂函数,框架使用该工厂函数来创建对象。
应用程序需要将ACE_FACTORY_DEFINE宏定义和组件定义放在一起,这样组件和组件的工厂函数就可以同时被编译成动态库。在配置文件中,保存着动态库的目录和名称,这样框架就可以根据配置文件找到动态库。框架的整个处理流程分为以下几步:
- 首先,框架通过动态库的目录和名称打开动态库。
- 接着,框架通过配置文件找到配置组件的工厂函数名称,有了该名称,框架就从打开的动态库中定位到工厂函数符号的地址,调用该工厂函数,框架就创建了配置组件的实例。
- 最后,框架通过调用配置组件实例的模板函数实现配置组件的功能。
4.7 组件配置器设计的分析
组件配置器时ConfigUR按投入框架的核心,与Reactor框架的Reactor管理器相对应,它即管理着整个Configurator框架,又提供了一系列的接口,用于应用程序访问和控制框架。组件配置器与Reactor管理器不同,它不需要处理事件,其主要任务是解析配置文件,然后将配置文件的指令传递给配置组件,使配置组件按照配置文件的指令进行工作。
组件配置器的功能由多个类协助完成,这些类的关系如图4-4所示。
图4-4 组件配置器类结构图
其中:
- ①ACE_Service_Config类是应用程序访问Configurator框架的通道,框架的所有控制接口由这个类提供。
- ②ACE_Service_Gestalt类是组件配置器的真正实现,组件配置器是进程级的,一个进程只要一个组件配置器实例,ACE_Service_Gestalt类是线程级的,不同线程即可以共享一个全局的ACE_Service_Gestalt实例,也可以拥有独立的ACE_Service_Gestalt实例。
4.7.1 组件配置器控制接口的分析
ACE_Service_Config类是应用程序访问Configurator框架的接口,框架的控制接口都由这个类提供,其代码清单如下:
// 代码在ace/Service_Config.h中
class ACE_Export ACE_Service_Config
{
/// The Instance, or the global (default) configuration context.
/// The monostate would forward the calls to that instance. The TSS
/// will point here
/*instance_用于保存默认的ACE_Service_Gestalt对象指针,组件配置器接口的很多调用都将转给对象*/
ACE_Intrusive_Auto_Ptr<ACE_Service_Gestalt> instance_;
/// A helper instance to manage thread-specific key creation.
/// Dependent on the syncronization mutex ACE uses, the corresponding
/// partial template instantiation will perform the right services
/// that have to do with managing thread-specific storage. Note that,
/// for single-threaded builds they would do (next to) nothing.
/*threadkey_的功能依赖于类的模板参数,如果是多线程环境,那么它用于线程私有存储,如果是单线程,那么它的操作都为空*/
ACE_Threading_Helper<ACE_SYNCH_MUTEX> threadkey_;
public:
//...省略
private:
/// Have we called ACE_Service_Config::open() yet?
bool is_opened_; // is_opened_是配置器打开标志
#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
/// Synchronization variable for open, etc.
mutable ACE_SYNCH_MUTEX lock_; // lock_是同步锁
#endif /* ACE_MT_SAFE */
/// True if reconfiguration occurred.
static sig_atomic_t reconfig_occurred_;// reconfig_occurred_是配置改变标志,初始化是0
// = Set by command-line options.
/// Shall we become a daemon process?
/*be_a_daemon_由命令行参数设置,指示是否将当前进程置为守护进程,初始化是false*/
static bool be_a_daemon_;
/// Pathname of file to write process id.
/*pid_file_name_用于记录当前进程号的文件名,初始化是0*/
static ACE_TCHAR *pid_file_name_;
/// Number of the signal used to trigger reconfiguration.
/*信号量,用于通知配置更新,初始值是SIGHUP*/
static int signum_;
/// Handles the reconfiguration signals.
/*signal_handler_是配置管理器使用的信号量事件处理句柄,它是一个Adapter*/
static ACE_Sig_Adapter *signal_handler_;
/// Pointer to the Singleton (ACE_Cleanup) Gestalt instance.
/// There is thread-specific global instance pointer, which is used to
/// temporarily change which Gestalt instance is used for static service
/// registrations.
///
/// A specific use case is a thread, which loads a _dynamic_ service from
/// a DLL. If the said DLL also contains additional _static_ services,
/// those *must* be registered with the same configuration repository as
/// the dynamic service. Otherwise, the DLL's static services would be
/// registered with the global Gestalt and may outlive the DLL that
/// contains their code and perhaps the memory in which they are in.
/// This is a problem because if the DLL gets unloaded (as it will, if
/// it was loaded in an instance of Gestalt), the DLL's memory will be
/// deallocated, but the global service repository will still "think"
/// it must finalize the (DLL's) static services - with disastrous
/// consequences, occurring in the post-main code (at_exit()).
/*ACE_Service_Config_Guard用于指向一个ACE_Service_Gestalt实例,用来在注册静态配置组件时临时改变当前ACE_Service_Gestalt实例指针。当一个线程从动态库中装载动态配置组件时,如果该动态配置组件还包含一个静态配置组件,那么它们必须被注册到相同的配置仓库中。否则,当动态库被卸载时,动态配置组件的内存将会被释放,但是静态配置组件仓库还拥有静态配置组件内存,这将会main函数退出时,造成灾难性的后果。*/
/// This class needs the intimate access to be able to swap the
/// current TSS pointer for the global Gestalt.
/*使用ACE_Service_Config_Guard友元可与改变和恢复每个线程的ACE_Service_Gestalt实例*/
friend class ACE_Service_Config_Guard;
/// The helper needs intimate access (when building with no threads)
/*下面两个友元用于线程私有存储*/
friend class ACE_Threading_Helper <ACE_Thread_Mutex>;
friend class ACE_Threading_Helper <ACE_Null_Mutex>;
};
ACE_Service_Config类有一个数据成员ACE_Service_Gestalt指针,Configurator框架的管理功能由ACE_Service_Gestalt类实现。
在默认情况下,所有调用Configurator框架接口的线程都有一个共享的ACE_Service_Gestalt实例,当然,应用程序也可以通过接口为线程设置一个独有的ACE_Service_Gestalt实例。ACE_Service_Config类还是一个参数化的工厂,工厂函数可以用于创建不同类型的ACE_Service_Type_Impl对象。
为了支持每个线程都可以拥有独立的ACE_Service_Gestalt对象这一功能,ACE设计了ACE_Threading_Helper类,它是对线程私有存储的简单封装,并且只在Configurator框架中使用。
Configurator框架对静态配置组件和动态配置组件的处理流程有很大的区别:
- ①静态配置组件使用static静态变量在main函数之前将静态配置组件的信息注册到框架中,由于发送在main函数之前,所以在注册静态配置组件的信息时使用的是全局的ACE_Service_Gestalt对象。
- ②动态配置组件的注册发生在应用程序启动后,所以对于动态配置组件,它的信息即可以注册在全局的ACE_Service_Gestalt对象中,也可以注册在局部的ACE_Service_Gestalt对象中。在默认情况下,框架并不支持静态配置,应用程序必须通过参数显示打开静态配置功能。
(1)open函数
open函数是Configurator框架的入口函数。对于应用程序来说,一般只调用open函数就可以启动整个Configurator框架。open函数会调用非常多的函数,下面是它的顺序图。
图4-5 open函数顺序图
open函数完成3个功能:
- ①一是调用ACE_Service_Config类的parse_args_i函数,对应用程序的参数进行解析;
- ②二是调用ACE_Service_Config类的open_i函数,完成ACE_Service_Config对象的初始化;
- ③三是调用ACE_Service_Gestalt类的open函数,完成组件配置。
(2)parse_args_i函数
parse_args_i函数用于解析输入的部分命令行参数,这些参数主要用于设置Configurator框架进程级的属性。
parse_args_i函数使用ACE的命令行解析模块对输入的参数进行解析,并做出相应的处理。对这些配置选项的描述如下:
- ①p选项表示配置用于保存进程号的文件名,将文件名保存在pid_file_name_数据成员中。
- ②b选项表示将应用程序启动为守护程序。
- ③s选项表示应用程序配置信号量用于通知Configurator框架配置发生了改变。如果应用程序配置了信号量,那么Configurator框架会将该信号量注册到Reactor框架中,此时信号量的事件处理器时signal_handler_。
(3)open_i函数
在parse_args_i函数中,我们解析了部分命令行参数和参数选项,接下来需要对这些参数进行进一步处理。open_i函数和parse_args_i函数的信号量处理是重复的,因此,二者既可以单独使用,也可以串行使用,这就要求它们必须有自己的信号量处理功能,因此导致了信号量处理的重复。
4.7.3 组件配置器实现的分析
ACE_Service_Gestalt类是组件配置器的具体实现。ACE_Service_Gestalt类的代码清单如下。
// 代码在ace/Service_Gestalt.h中
class ACE_Export ACE_Service_Gestalt : private ACE_Copy_Disabled
{
public:
//...省略
protected:
/// Do we own the service repository instance, or have only been
/// given a ptr to the singleton?
/*svc_repo_isowned_用于标志ACE_Service_Gestalt对象是否拥有独立的配置组件仓库*/
bool svc_repo_is_owned_;
/// Repository size is necessary, so that we can close (which may
/// destroy the repository instance), and then re-open again.
/*svc_repo_size_表示配置组件仓库的大小,这样仓库就可以被关闭和重新打开*/
size_t svc_repo_size_;
/// Keep track of the number of times the instance has been
/// initialized (opened). "If so, we can't allow <yyparse> to be called since
/// it's not reentrant" is the original motivation, but that does not seem
/// to be the case anymore. This variable is incremented by the
/// <ACE_Service_Gestalt::open> method and decremented by the
/// <ACE_Service_Gestalt::close> method.
/*is_open_用于记录ACE_Service_Gestalt对象被初始化的次数,最初是为了保护yyparse函数,因此该函数是一个非可重入函数。现在它只在open函数中执行递增操作,在close函数中执行递减操作*/
int is_opened_;
/// Indicates where to write the logging output. This is typically
/// either a STREAM pipe or a socket
/*logger_key_用于记录日志的输出,在通常情况下,它是一个流或是一个Socket*/
const ACE_TCHAR *logger_key_;
/// Should we avoid loading the static services?
/*no_static_svcs_表示是否装载静态配置*/
bool no_static_svcs_;
/// Queue of services specified on the command-line.
/*svc_queue_用于记录通过命令行输入的配置*/
ACE_SVC_QUEUE* svc_queue_;
/**
* Queue of svc.conf files specified on the command-line.
* @@ This should probably be made to handle unicode filenames...
*/
/*svc_conf_file_queue_用于记录通过命令行输入的配置文件名,支持Unicode字符*/
ACE_SVC_QUEUE* svc_conf_file_queue_;
/// The service repository to hold the services.
/*配置组件仓库*/
ACE_Service_Repository* repo_;
/// Repository of statically linked services.
/*静态配置描述符*/
ACE_STATIC_SVCS* static_svcs_;
/// Repository of statically linked services for which process
/// directive was called, but the service is not already a member of
/// the static_svcs_ list.
/*已处理的静态配置集,用于保存被函数process_directive处理过,但是还没有被添加到static_svcs_链表中的静态配置*/
ACE_PROCESSED_STATIC_SVCS* processed_static_svcs_;
/// Support for intrusive reference counting
/*refcnt_用于引用计数操作*/
ACE_Atomic_Op<ACE_SYNCH_MUTEX, long> refcnt_;
public:
static void intrusive_add_ref (ACE_Service_Gestalt*);
static void intrusive_remove_ref (ACE_Service_Gestalt*);
}; /* class ACE_Service_Gestalt */
(1)open函数
ACE_Service_Gestalt的open函数主要完成两个功能:
- ①一是参数解析,由函数parse_args_i完成;
- ②二是配置文件的解析和处理,由函数open_i完成。
(2)parse_args_i函数
在ACE_Service_Config类的parse_args_i函数中,我们已经解析了部分命令行选项,这些选项其实和配置组件没有什么关系。和配置组件相关的选项都在ACE_Service_Gestalt的parse_args_i函数中。
为了提供Configurator框架的灵活性,尽可能满足应用程序的配置需求,因此框架提供了丰富的配置选项。对这些配置选项的描述如表4-1所示。
表4-1 命令行选项及其说明
其中,f选项表示通过命令行设置文件的名称,选项参数是配置文件名。Configurator框架支持多个配置文件,即可以使用默认的配置文件(svc.conf),也可以使用通过f选项输入的配置文件。S选项表示通过命令行输入配置,所有配置文件中的信息都可以通过该选项输入。
(2)open_i函数
在open_i函数中,进行配置文件的处理。主要完成4部分的功能:
①初始化
②日志处理
③配置文件处理
如果用户指定了配置文件,那么应检查该配置文件是否和默认的配置文件同名。如果不同名,并且默认的配置文件存在,说明需要将默认的配置文件加入到配置文件队列中,将add_default设为true。这说明默认配置文件和应用程序制定的配置文件是可以同时存在的。如果应用程序通过S选项输入指令,那么同样需要检查默认的配置文件是否存在。这也说明默认配置文件、命令行输入的指令三者是可以共存的。
④对配置文件和指令进行解析和处理。
(4)process_directives_i函数
process_directives_i函数将对文件的处理交给语法分析器----ace_yyparse函数。
4.7.3 语法分析器的分析
要将配置文件中的配置转化为对配置组件的动作,首先需要解析配置文件的内容,然后根据内容调用组件的相关接口,这样才能实现组件的功能。整个语法分析的流程如图4-6所示。其中scan函数用于逐词扫描配置的内容,将描述的语义结果返回给ace_yyparse函数,ace_yyparse函数根据语义结果对组件执行相应的动作。
图4-6 语法分析顺序图
4.8 动态库符号定位的分析
示例1的配置文件中保存了组件的动态库目录和名称、组件的工厂函数名称,框架在解析了配置文件之后,要创建真正的组件实例,必须打开动态库,找到工厂函数的符号地址,然后创建组件实例。ACE把这些工作全部交给ACE_Location_Node类及其子类。如图4-7所示描述了ACE_Location_Node类和其子类的结构。
图4-7 ACE_Location_Node和其子类结构图
ACE_Location_Node类用于描述动态库的信息和记录动态库的符号地址。它也可以用在静态配置中记录静态符号的地址。ACE_Location_Node类本身用于描述动态库信息,而定位符号的地址由其子类来完成。ACE_Location_Node类有一个纯虚函数symbol,该函数用于在不同的情况下定位符号地址。
C++有两种方式生成对象,一种是静态的方式,另一种是动态方式。静态方式使用类直接定义对象,动态方式则需要使用new来动态生产对象。图4-7中所示的ACE_Location_Node用来定位静态生成的对象,ACE_Function_Node则用于定位动态成的对象。
4.8.1 ACE_Location_Node类分析
ACE_Location_Node类是一个接口,它的代码清单如下。
// 代码在ace/Parse_Node.h中
class ACE_Location_Node
{
public:
ACE_Location_Node (void);
const ACE_DLL &dll (void);
const ACE_TCHAR *pathname (void) const;
void pathname (const ACE_TCHAR *h);
int dispose (void) const;
virtual ~ACE_Location_Node (void);
virtual void set_symbol (void *h);
/// Will update the yyerrno member and/or corresponding configuration
/// repository
virtual void *symbol (ACE_Service_Gestalt *cfgptr,
int &yyerrno,
ACE_Service_Object_Exterminator * = 0) = 0;
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
protected:
int open_dll (int & yyerrno);
/// Pathname to the shared library we are working on.
/*pathname_用于保存配置文件中的动态库目录和名称*/
const ACE_TCHAR *pathname_;
/**
* Flag indicating whether the Service_Object generated by this
* Location Node should be deleted or not
* (ACE_Service_Type::DELETE_OBJ.)
*/
/*must_delete_是删除配置组件对象的标志*/
int must_delete_;
/// The open shared library.
/*dll_是动态库接口*/
ACE_DLL dll_;
/// Symbol that we've obtained from the shared library.
void *symbol_;// 用于保存配置组件的对象的地址
private:
ACE_UNIMPLEMENTED_FUNC (ACE_Location_Node (const ACE_Location_Node&))
ACE_UNIMPLEMENTED_FUNC (ACE_Location_Node& operator= (const ACE_Location_Node&))
};
配置组件有两种生成方式,静态生成和动态生成。对于动态生成的对象,框架必须在使用完成后,将其删除,静态对象则无须删除。must_delete_为是否删除对象的标志,其初始化值为0,表示不需要删除,对于动态生成的对象,必须将该标志置为1,表示对象需要删除。
ACE_Location_Node类的open_dll函数用于打开动态库,它使用了ACE_DLL的接口。
4.8.2 ACE_Object_Node类的分析
ACE_Object_Node类用于静态生成的对象,它使用静态对象的名称定位对象的符号地址。其代码清单如下:
// 代码在ace/Parse_Node.h中
class ACE_Object_Node : public ACE_Location_Node
{
public:
ACE_Object_Node (const ACE_TCHAR *pathname, const ACE_TCHAR *obj_name);
virtual void *symbol (ACE_Service_Gestalt *config,
int &yyerrno,
ACE_Service_Object_Exterminator * = 0);
virtual ~ACE_Object_Node (void);
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
private:
/// Name of the object that we're parsing.
/*object_name_是静态对象的名称*/
const ACE_TCHAR *object_name_;
private:
ACE_UNIMPLEMENTED_FUNC (ACE_Object_Node (const ACE_Object_Node&))
ACE_UNIMPLEMENTED_FUNC (ACE_Object_Node& operator= (const ACE_Object_Node&))
};
object_name_是对象名称。该对象名称是配置组件静态生成的对象(通过直接定义的方式)的名称,所以通过该名称定位到的符号地址就是该静态对象的地址。
symbol函数用于定位静态对象的地址。
4.8.4 ACE_Function_Node类的分析
与ACE_Object_Node类相对比,ACE_Function_Node类用于动态生成的对象。ACE_Function_Node类使用对象工程函数创建对象,然后返回对象的地址。其代码清单如下:
// 代码在ace/Parse_Node.h中
class ACE_Function_Node : public ACE_Location_Node
{
public:
ACE_Function_Node (const ACE_TCHAR *pathname, const ACE_TCHAR *func_name);
virtual void *symbol (ACE_Service_Gestalt *config,
int &yyerrno,
ACE_Service_Object_Exterminator *gobbler = 0);
virtual ~ACE_Function_Node (void);
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
private:
/// Return mangled function name that takes into account ACE
/// versioned namespace.
/**
* This function embeds the ACE versioned namespace name into the
* original function name if versioned namespace support has been
* enabled and the original function name conforms to the ACE
* Service Object factory function naming conventions. For example
* "@c _make_Foo" becomes "@c make_ACE_5_4_7_Foo".
* @par
* If versioned namespace support is disabled or the factory
* function name does conform to ACE conventions, no mangling will
* occur and the verbatim function name is returned.
*
* @return Function name that takes into account versioned namespace
* name. Caller is responsible for calling operator
* delete[] or ACE::strdelete() on the returned string.
*/
ACE_TCHAR * make_func_name (ACE_TCHAR const * func_name);
private:
/// Name of the function that we're parsing.
/*function_name_是对象工程函数名称*/
const ACE_TCHAR *function_name_;
private:
ACE_UNIMPLEMENTED_FUNC (ACE_Function_Node (const ACE_Function_Node&))
ACE_UNIMPLEMENTED_FUNC (ACE_Function_Node& operator= (const ACE_Function_Node&))
};
function_name_用于保存对象工厂的函数名,该函数就是在配置文件中配置的_make_xxx函数,同时也是在ACE_FACTORY_DEFINE宏中定义的对象工厂函数。框架就是通过配置文件获得了对象工厂函数名,然后通过动态库定位到该函数的地址,然后创建配置组件对象。
symbol函数用于定位动态对象的地址。
4.9 配置组件仓库的分析
在应用程序中可以有多个配置组件共同工作,这就需要一个组件仓库来管理这些配置组件,这个仓库就是ACE_Service_Repository类。和Reactor框架的I/O事件处理器仓库一样,它的主要作用是存储、查找和删除配置组件。配置组件仓库即可以由全部ACE_Service_Gestalt实例共享,也可以每个ACE_Service_Gestalt实例独有一份。为了满足全局共享的要求,ACE_Service_Repository类提供了Singleton设计模式接口。
ACE_Service_Repository类代码清单如下:
// 代码在ace/Service_Repository.h中
class ACE_Export ACE_Service_Repository
{
public:
// ...省略
/// The typedef of the array used to store the services.
/*array_type是保存配置组件的容器类型*/
typedef ACE_Array_Map <size_t, const ACE_Service_Type*> array_type;
/// Contains all the configured services.
array_type service_array_; // service_array_用于保存所有的配置组件
/// Pointer to a process-wide ACE_Service_Repository.
/*配置组件仓库指针,用于Singleton设计模式*/
static ACE_Service_Repository *svc_rep_;
/// Must delete the @c svc_rep_ if true.
/*delete_svc_rep_是配置组件仓库的删除标志*/
static bool delete_svc_rep_;
#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
/// Synchronization variable for the MT_SAFE Repository
mutable ACE_Recursive_Thread_Mutex lock_;
#endif /* ACE_MT_SAFE */
};
从容器的定义中我们可以发现,配置组件仓库中存储的是ACE_Service_Type对象,而不是配置组件对象,这没有关系,因为每个ACE_Service_Type对象有一个间接的指针指向了配置组件对象,通过ACE_Service_Type对象可以找到配置组件对象。
(1)find函数
(2)remove函数
(3)suspend函数
(4)resume函数
4.10 配置组件类型的分析
配置组件仓库中保存的是ACE_Service_Type类型的对象,而并非配置组件ACE_Service_Object类型的对象。实际上,Configurator框架支持3中不同类型的配置组件,①一种是本章分析的仅继承自ACE_Service_Object接口的配置组件,②另外两种组件和ACE的Streams框架相关,ACE_Service_Object是 它们的公共接口。虽然这3种配置组件的操作有很大的不同,但是又没有必要设计3个配置组件仓库,所以ACE设计了ACE_Service_Type类,由它来封装3中不同的配置组件。在ACE_Service_Type类的实现内部,使用ACE_Service_Object_Type、ACE_Module_Type和ACE_Stream_Type这3个类来对应上述3种不同的配置组件。ACE_Service_Type类的设计使用了Bridge设计模式,其结构如图4-8所所示。其中ACE_Service_Object_Type是示例1中使用的类型,它在配置文件中通过Service_Object*来指定。
图4-8 ACE_Service_Type相关的结构图
4.10.1 ACE_Service_Type类
每一个配置组件都有一个唯一的名称,在打开组件时,框架会创建一个ACE_Service_Type对象,保存该组件的信息,然后将该ACE_Service_Type对象插入到配置组件仓库中。ACE_Service_Type类定义如下。
// 代码在ace/Service_Object.h中
class ACE_Export ACE_Service_Type
{
public:
enum
{
/// Delete the payload object.
/*DELETE_OBJ表示在调用组件fini函数后删除组件对象*/
DELETE_OBJ = 1,
/// Delete the enclosing object.
/*DELETE_THIS表示在调用组件fini函数后删除ACE_Service_Type_Impl对象*/
DELETE_THIS = 2
};
enum
{
SERVICE_OBJECT = ACE_SVC_OBJ_T,
MODULE = ACE_MODULE_T,
STREAM = ACE_STREAM_T,
INVALID_TYPE = -1
};
// ...省略
private:
/// Humanly readible name of svc.// name_是配置组件名称
const ACE_TCHAR *name_;
/// Pointer to C++ object that implements the svc.
/*type_对应Bridge设计模式的实现者*/
const ACE_Service_Type_Impl *type_;
/// ACE_DLL representing the shared object file (non-zero if
/// dynamically linked).// dll_是组件的动态库接口
mutable ACE_DLL dll_;
/// true if svc is currently active, otherwise false.
bool active_;// active_是组件活动标志
/// true if @c fini on @c type_ has already been called, otherwise false.
bool fini_already_called_; // fini_already_called_是组件fini函数调用标志
};
4.10.2 ACE_Service_Type_Impl类
ACE_Service_Type_Impl类是3中不同配置组件的公共接口,同时还用于保存组件的一些关键信息,定义如下:
// 代码在ace/Service_Types.h中
class ACE_Export ACE_Service_Type_Impl
{
public:
// ...省略
protected:
/// Name of the service.// name_是配置组件名称
const ACE_TCHAR *name_;
/// Pointer to object that implements the service. This actually
/// points to an ACE_Service_Object, ACE_Module, or ACE_Stream.
/*obj_是配置组件对象的指针,它指向一个ACE_Service_Object、ACE_Module或ACE_Stream对象*/
void *obj_;
/// Destroy function to deallocate obj_.
/*gobbler_指向配置组件的销毁函数*/
ACE_Service_Object_Exterminator gobbler_;
/// Flags that control serivce behavior (particularly deletion).
u_int flags_;// flags_用于控制对象的删除属性
/// type of this service
/// Used to properly manage the lifecycle of ACE_Modules and ACE_Streams
/// during shutdown
int service_type_; // service_type_表示配置组件的类型
};
其中,flags_用于控制对象的删除属性,它的值是ACE_Service_Type::DELETE_OBJ和ACE_Service_Type::DELETE_THIS一个或两个的组合。service_type_表示配置组件类型,包括ACE_Service_Type::SERVICE_OBJECT、ACE_Service_Type::MODULE和ACE_Service_Type::STREAM。
4.10.3 ACE_Service_Object_Type类
ACE_Service_Object_Type类是示例1中的配置组件使用的类型,它将init、fini、suspend、resume和info等函数直接转给了配置组件对象。
4.10.4 ACE_Service_Type_Factory类
ACE_Service_Type_Factory类是一个工厂,用于创建ACE_Service_Type对象,当然还包括它的Implementor。ACE_Service_Type_Factory类的定义如下。
// 代码在ace/Parse_Node.h中
class ACE_Service_Type_Factory
{
public:
ACE_Service_Type_Factory (ACE_TCHAR const *name,
int type,
ACE_Location_Node *location,
int active);
~ACE_Service_Type_Factory (void);
/*ACE_Service_Type_Factory类是一个工厂,它的工厂函数----make_service_type*/
ACE_Service_Type *make_service_type (ACE_Service_Gestalt *pcfg) const;
ACE_TCHAR const* name (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
private:
/**
* Not implemented to enforce no copying
*/
ACE_UNIMPLEMENTED_FUNC
(ACE_Service_Type_Factory(const ACE_Service_Type_Factory&))
ACE_UNIMPLEMENTED_FUNC
(ACE_Service_Type_Factory& operator=(const ACE_Service_Type_Factory&))
private:
ACE_TString name_;// name_是组件名称
int type_;// type_是组件类型
ACE_Auto_Ptr<ACE_Location_Node> location_;// location_是组件的符合信息
int is_active_;// is_active_是组件活动标志
};
4.11 指令解析功能的分析
配置文件中有很多关键字,包括①dynamic和static,表示组件的编译类型;②suspend、resume和remove表示对组件的动作指令,③Module、STREAM和Service_Object表示组件的类型;④active和inactive表示组件初始化状态。
框架为dynamic、static、suspend、resume和remove关键字的解析创建了一个统一的接口----ACE_Parse_Node类,每一个关键字都和一个ACE_Parse_Node接口的子类对应,它们的关系如图4-9所示。ACE_Parse_Node接口有一个纯虚函数apply,它的子类通过该函数实现各自的指令。
图4-9 ACE_Parse_Node类和其子类的结构图
ACE_Parse_Node接口的定义如下所示。
// 代码在ace/Parse_Node.h中
class ACE_Parse_Node
{
public:
ACE_Parse_Node (void);
explicit ACE_Parse_Node (const ACE_TCHAR *name);
virtual ~ACE_Parse_Node (void);
ACE_Parse_Node *link (void) const;
void link (ACE_Parse_Node *);
/// Will update the yyereno member and/or the corresponding configuration
virtual void apply (ACE_Service_Gestalt *cfg, int &yyerrno) = 0;
const ACE_TCHAR *name (void) const;
void print (void) const;
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
private:
const ACE_TCHAR *name_;// name_表示组件名称
ACE_Parse_Node *next_; // next_用于STREAM和MODULE类型的组件
private:
ACE_UNIMPLEMENTED_FUNC (ACE_Parse_Node (const ACE_Parse_Node&))
ACE_UNIMPLEMENTED_FUNC (ACE_Parse_Node& operator= (const ACE_Parse_Node&))
};
在图4-9所示的类中,ACE_Dynamic_Node和ACE_Static_Node的apply函数最终调用配置组件的init函数,同时将该组件插入到配置组件仓库中,其他几个类的apply函数调用组件的相关指令。
4.12 配置文件解析流程的分析
接下来,我们将框架配置文件的步骤及框架对解析的结果所采取的动作详细描述一遍。
ace_yyparse是Configurator框架的一个非常重要的函数,它根据词法分析器yylex返回的词法语义创建配置附近对象,并调用配置组件的接口函数。下面我们对该函数的部分功能进行分析,分析的配置文件是示例1中的svc.conf文件。词法分析器yylex会分析配置文件中的每一个单词(以空格和冒号分隔)。ace_yyparse会保存每次返回的结果,当返回的信息达到一定量时,便会采取一定的动作。
框架对配置文件的处理过程如下:
(1)步骤1 读取配置文件的内容:dynamic DynamicObj Service_Object* ./libthis_dll.so:_make_DynamicObj()。
当yylex分析完这一句时,ace_yyparse根据分析的结果获得如下信息。
- ①dynamic:表示是动态配置。
- ②DynamicObj:配置组件的名称。
- ③Service_Object*:配置组件的类型是ACE_Service_Type::SERVICE_OBJECT。
- ④./libthis_dll.so:动态库所在的目录和动态库的名称。
- ⑤ _make_DynamicObj():对象工厂函数。
采取的动作如下:
创建ACE_Function_Node对象,参数分别是./libthis_dll.so和_make_DynamicObj()。因为示例1使用的对象工厂函数创建对象的方式,所以先创建ACE_Function_Node对象。
(2)步骤2 读取配置文件内容:""(传递给init函数的参数)。
采取的动作如下:
首先创建ACE_Service_Type_Factory对象,参数分别为DynamicObj和ACE_Service_Type::SERVICE_OBJECT,location为步骤1中的location_node,active为1。
然后创建ACE_Dynamic_Node对象,其参数stf为步骤2中的svc_record_,param="",在示例1中参数为空。
最后调用ACE_Dynamic_Node的apply函数。apply函数在其他函数的帮助下创建配置组件对象,调用配置组件的init函数,最后将配置组件插入到配置组件仓库中。
(3)步骤3 读取配置文件内容:suspend DynamicObj。
采取的动作如下:
首先创建ACE_Suspend_Node对象,name="DynamicObj"。
然后调用ACE_Suspend_Node的apply函数,参考步骤2。最终调用配置组件接口的suspend函数,同时将组件仓库中的配置组件active_标志置为false。
(4)步骤4 读取配置文件内容:resume DynamicObj。
采取的动作如下:
首先创建ACE_Resume_Node对象,name="DynamicObj"。
然后调用ACE_Resume_Node的apply函数,参考步骤2。调用配置组件接口的resume函数,同时将配置仓库中的配置组件active_标志置为true。
(5)步骤5 读取配置文件内容:remove DynamicObj。
采取的动作如下:
首先创建ACE_Remove_Node对象。
然后调用ACE_Remove_Node的apply函数,参考步骤2。在配置仓库中,将该配置组件删除,在析构函数中配置组件接口的fini函数。
至此示例1的分析已经结束。
4.13 Configurator框架应用示例2
动态配置有两种方式,①一种是使用对象工厂函数动态地创建对象,②另一种是静态创建组件对象。示例2采用了静态创建的方式。
4.13.1 可配置组件
继续使用示例1中的代码,只是做了一些修改。如下
和采用对象工厂函数的方式相比,我们发现,这里并没有使用ACE_FACTORY_DEFINE宏,而是直接定义了一个静态对象--obj。
4.13.2 配置文件
配置文件也需要做一些改变,如下
从配置文件中,我们可以发现,这里删除了配置组件的工厂函数,直接使用了静态对象的名称obj。
4.13.3 配置文件解析流程的分析
读取配置文件的内容:dynamic DynamicObj Service_Object* ./libthis_dll.so:obj。
采取的动作如下:
创建ACE_Object_Node对象,参数是./libthis_dll.so和obj。
其他的步骤同4.11节中的示例1的步骤1~步骤5.
我们可以发现示例1和示例2的区别在于创建配置对象的方式不同,反映在解析中则是ACE_Funciton_Node类和ACE_Object_Node类的区别。
4.14 配置改变
如果组件的配置发送变化,那么Configurator框架是如何知道的呢?又是如何来处理的呢?在示例1和示例2中,我们采用信号量的方式:一旦应用程序发出SINGINT信号,Reactor框架就会接收并处理该信号,调用ACE_Service_Config:;reconfig_occurred函数将reconfig_occurred_置为1。Reactor框架的循环等待函数每一次循环都会电泳ACE_Reactor::check_reconfiguration函数,该函数会检查reconfig_occurred_标志,如果此标志位1,那么Configurator框架会重新读取配置文件,实现配置的动态改变。当然除了这种方式外,还可以使用Configurator框架提供的信号处理方式,在这种方式下,Configurator框架在初始化时会自动注册一个信号量事件,作为配置改变的通知事件。
4.15 Configurator框架应用示例3
动态配置的方式灵活性较高,但安全性不足。静态配置不如动态配置那样灵活,但是正是因为如此,才具有更好的灵活性。
4.15.1 静态配置组件
使用静态配置的组件不需要编译动态库,它使用静态编译的方式,直接链接到应用程序中。静态配置组件代码如下。
和动态配置组件相比,静态配置组件多使用了两个宏----ACE_STATIC_SVC_DEFINE宏和ACE_STATIC_SVC_REQUIRE宏。
ACE_STATIC_SVC_DEFINE宏展开如下:
通过宏展开后的代码,我们可以发现原来这个宏用于创建一个静态的ACE_Static_Svc_Descriptor对象,我们称之为静态配置描述对象。新创建的ACE_Static_Svc_Descriptor对象的各个字段的含义描述如下:
- ①StaticObj是静态配置组件的名称,配置文件中的组件名称应该和它一致。
- ②ACE_SVC_OBJ_T表示组件的类型。
- ③_make_StaticObj()是配置组件的工厂函数,它应该和ACE_FACTORY_DEFINE宏定义的工厂函数一致。
- ④ACE_Service_Type::DELETE_THIS|ACE_Service_Type::DELETE_OBJ表示组件的清除属性。
- ⑤1表示组件创建后的状态。
ACE_STATIC_SVC_REQUIRE宏的定义与编译器有关,但是不同的编译器间差别不大。在Linux环境下,该宏定义如下:
由此可见,该宏的作用是创建一个static对象,该对象的构造函数将ACE_STATIC_SVC_DEFINE宏创建的静态配置描述对象插入到Configuration框架中。
4.15.2 配置文件
配置文件如下:
静态配置组件的配置文件非常简单,其过程如下:
- ①首先,不需要配置动态库名称和目录。因为是静态配置,所以没有动态库,也就不存在动态库名称之类的配置。
- ②其次,不需要显式配置工厂函数。静态配置的工厂函数已经在ACE_Static_Svc_Descriptor对象中定义,因此配置文件中不需要显式配置工厂函数。
- ③最后,不需要显式配置组件类型。组件类型也已经在ACE_Static_Svc_Descriptor对象中定义。因此,配置文件中不需要配置组件类型。
4.15.3 静态配置组件分析
每个静态配置组件都有一个ACE_Static_Svc_Descriptor对象,该对象在main函数之前插入到框架的静态配置描述集中。在框架启动后,其会对静态配置描述集中的每个静态配置描述进行装载处理。静态配置描述通过insert磺酸钠插入到静态配置描述集中,框架通过load_static_svc函数装载静态配置描述。
(1)insert函数
insert函数将静态配置描述对象插入到框架的静态配置描述集中。正是由于该动作发生在main函数之前,才使得进入main函数之后的load_static_svcs函数有意义。
(2)load_static_svcs函数
静态配置描述使用的是ACE_Static_Svc_Descriptor类型,动态配置组件仓库中使用的是ACE_Service_Type类型,为了将静态配置组件和动态组件综合在一起,使用统一的数据结构和处理流程,需要将ACE_Static_Svc_Descriptor对象转化为ACE_Service_Type对象,load_static_svcs函数用于完成上述功能。
load_static_svcs函数中遍历静态配置描述集static_svcs_,对每个静态配置描述对象调用process_directive函数,其中force_replace标志是1。而process_directive函数又会调用process_directive_i函数。
(3)process_directive_i函数
process_directive_i函数将静态描述符信息转化为ACE_Service_Type对象,然后插入到配置组件仓库中,这样就可以和动态配置统一起来。
(4)配置文件解析流程分析
对静态配置组件执行装载后,配置组件仓库中就有了示例3中的静态配置组件对象了,下面来看一下ace_yyparse函数对静态配置的处理流程。静态配置的配置文件相对简单,它不需要动态库名,不需要对象工厂函数(在宏定义中已经加入到框架中),不需要配置组件类型。下面具体解析配置文件的解析流程。
读取配置文件的内容:static StaticObj ""(示例3的配置参数为空)。采取的动作如下:
首先创建ACE_Static_Node对象,参数是StaticObj和""。
然后调用apply函数,最终会调用配置组件的init函数。其他指令的操作与示例1是一样的。
【我们比较熟悉用Reactor框架处理网络应用中的各种事件,而Configration框架相对来说比较难理解。ACE Configuration为实现可配置组件提供了非常好的实现思想和实现方法。】
参考文献:
[1] ACE技术内幕:深入解析ACE架构设计与实现原理