[ACE系列] ACE学习
ACE 学习
作者:陈珍敬
ACE 的配置(window )
(使用VC ++)安装:
从网上下载相应源码 ―――― 根据提示编辑 config.h
文件,并放置在
ACE_ROOT/ace 目录下。
用VC 打开ACE_ROOT/ace/ace.dsw ,并编译,编译后会在ACE_ROOT/lib 目录下生成两个库:ACEd.dll( 动态库) 和ACEd.lib (静态库) 。
链接: 把ACEd.dll( 动态库) 和ACEd.lib (静态库)复制到目录ACE_ROOT/ace 下,因为这是默认的静态库链接路径。或者可以修改静态库链接路径:project->setting->link->object/library 填入ACE_ROOT/lib 。
执行: If you use the dynamic libraries, make sure you include ACE_ROOT/bin in your PATH whenever you run programs that uses ACE. Otherwise you may experience problems finding ace.dll or aced.dll.
就是在程序执行时,如果使用ACE 的动态库,必须修改系统环境变量PATH 的值,把包含ACEd.dll 的路径添加到PATH 中。修改如下:电脑属性 ―― 高级 ―― 环境变量 ―― 系统变量 ―― Path (修改)。或者把ACEd.dll 添加到系统默认的搜索路径之中,如添加到c:/window/system32 中。
在VC ++下使用ACE
新建工程 :使用new 菜单,选中project 。选取win32 console Applitation( 控制台应用程序) ,建立一个空项目。同时新建一个workspace 。
一个workspace 可以对应几个项目,一个项目对应一个程序。当然如何在一个workspace 管理多个工程现在还没搞清楚。Workspace 保存空间中的相应配置,而一个项目保存,自己项目下的项目配置。
添加头文件的搜索路径: tools->option->directories ,添加ace 头文件路径。当然,按照同样的办法,也可以修改库文件、执行文件、源文件的搜索路径,只需做相应选择就可以了。
修改项目的setting : 通过project->setting 进入,或是直接在项目名字上点击右键,选择setting 即可。把C/C++ 中的MLd 修改为MDd (多线程);把link 中input 中的库改为aced.lib ,同时additional path 中 ../../../ace 。这样设置就基本完成。
ACE
ACE 自适配通信环境 (Adaptive Communication Environment) 是面向对象的构架和工具包,它为通信软件实现了核心的并发和分布式模式。 ACE 中的组件可用于以下几种目的:
- 并发和同步
- 进程间通信 (IPC)
- 内存管理
- 定时器
- 信号
- 文件系统管理
- 线程管理
- 事件多路分离和处理器分派
- 连接建立和服务初始化
- 软件的静态和动态配置、重配置
- 分层协议构建和流式构架
- 分布式通信服务:名字、日志、时间同步、事件路由和网络锁定,等等。
目前ACE 适用的OS 平台包括:实时OS (VxWorks 、Chorus 、LynxOS 和pSoS ) 、大多数版本的UNIX (SunOS 4.x 和5.x; SGI IRIX 5.x 和6.x; HP-UX 9.x, 10.x 和11.x; DEC UNIX 3.x 和4.x; AIX 3.x 和4.x; DG/UX; Linux ; SCO; UnixWare; NetBSD 和FreeBSD )、Win32 (使用MSVC++ 和Borland C++ 的WinNT 3.5.x 、4.x 、Win95 和WinCE )以及MVS OpenEdition 。
在 ACE 构架中有三个基本层次:
- 操作系统( OS )适配层
- C++ 包装层
- 构架和模式层
第2 章 IPC SAP :进程间通信服务访问点 包装
ACE_IPC_SAP 类提供的一些函数是所有IPC 接口公有的。 有四个不同的类由此类派生而出,每个类各自代表ACE 包含的一种IPC SAP 包装类属。这些类封装适用于特定IPC 接口的功能。例如,ACE_SOCK 类包含的功能适用于BSD socket 编程接口,而ACE_TLI 包装TLI 编程接口。ACE_FIFO 类和 ACE_SPIPE 类。
socket 类属(ACE_SOCK )
类名 |
职责 |
ACE_SOCK_Acceptor |
用于被动的连接建立,基于BSD accept() 和listen() 调用。 |
ACE_SOCK_Connector |
用于主动的连接建立,基于BSD connect() 调用。 |
ACE_SOCK_Dgram |
用于提供基于UDP (用户数据报协议)的无连接消息传递服务。封装了sendto() 和receivefrom() 等调用,并提供了简单的send() 和recv() 接口 。 |
ACE_SOCK_IO |
用于提供面向连接的消息传递服务。封装了send() 、recv() 和write() 等调用。该类是ACE_SOCK_Stream 和ACE_SOCK_CODgram 类的基类 。 |
ACE_SOCK_Stream |
用于提供基于TCP (传输控制协议)的面向连接的消息传递服务。派生自ACE_SOCK_IO ,并提供了更多的包装方法。 |
ACE_SOCK_CODgram |
用于提供有连接数据报(connected datagram )抽象。派生自ACE_SOCK_IO ;它包含的open() 方法使用bind() 来绑定到指定的本地地址,并使用UDP 连接到远地地址。 |
ACE_SOCK_Dgram_Mcast |
用于提供基于数据报的多点传送(multicast) 抽象。包括预订多点传送组,以及发送和接收消息的方法 |
ACE_SOCK_Dgram_Bcast |
用于提供基于数据报的广播(broadcast) 抽象。包括在子网中向所有接口广播数据报消息的方法 |
表2-1 ACE_SOCK 中的类及其职责
第3 章 ACE 的内存管理
ACE 含有两组不同的类用于内存管理。
第一组是那些基于ACE_Allocator 的类 。这组类使用动态绑定和策略 模式 来提供灵活性和可扩展性。它们只能用于局部的动态内存分配 。
第二组类基于ACE_Malloc 模板类 。这组类使用C++ 模板和外部多态性 (External Polymorphism )来为内存分配机制提供灵活性。在这组类中的类不仅包括了用于局部动态内存管理的类,也包括了管理进程间共享内存的类 。这些共享内存类使用底层OS (OS )共享内存接口 。
3.1 分配器(Allocator)
分配器用于在ACE 中提供一种动态内存管理机制。在ACE 中有若干使用不同策略的分配器可用。这些不同策略提供相同的功能,但是具有不同的特性。 所有的分配器都支持ACE_Allocator 接口,因此无论是在运行时还是在编译时,它们都可以很容易地相互替换。这也正是灵活性之所在。
分配器 |
描述 |
ACE_Allocator |
ACE 中的分配器类的接口类 。这些类使用继承和动态绑定 来提供灵活性。 |
ACE_Static_Allocator |
该分配器管理固定大小的内存 。每当收到分配内存的请求时,它就移动内部指针、以返回内存 chunk (“大块”)。它还假定内存一旦被分配,就再也不会被释放 。 |
ACE_Cached_Allocator |
该分配器预先 分配内存池 ,其中含有特定数目和大小的内存 chunk 。这些 chunk 在内部空闲表( free list )中进行维护 ,并在收到内存请求( malloc() )时被返回。当应用调用 free() 时, chunk 被归还到内部空闲表 、而不是 OS 中。 |
ACE_New_Allocator |
为 C++ new 和 delete 操作符提供包装的分配器,也就是,它在内部使用 new 和 delete 操作符 ,以满足动态内存请求。 |
表 3-1 ACE 中的分配器
使用如下:
typedef ACE_Cached_Allocator<MEMORY_BLOCK,ACE_SYNCH_MUTEX> Allocator;
3.2 ACE_Malloc
Malloc 类集使用模板类 ACE_Malloc 来提供内存管理 。 ACE_Malloc 模板需要两个参数(一个是内存池,一个是池锁),以产生我们的分配器类。 当应用发出free() 调用时,ACE_Malloc 不会把所释放的内存返还给内存池,而是由它自己的空闲表进行管理。 当ACE_Malloc 收到后续的内存请求时,它会使用空闲表来查找可返回的空block 。因而,在使用ACE_Malloc 时,如果只发出简单的malloc() 和free() 调用,从OS 分配的内存数量将只会增加,不会减少。ACE_Malloc 类还含有一个remove() 方法,用于发出请求给内存池,将内存返还给OS 。该方法还将锁也返还给OS 。
池名 |
宏 |
描述 |
ACE_MMAP_Memory_Pool |
ACE_MMAP_MEMORY_POOL |
使用 <mmap(2)> 创建内存池。这样内存就可在进程间共享 了。每次更新时,内存都被更新到后备存储 (backing store) 。 |
ACE_Lite_MMAP_Memory_Pool |
ACE_LITE_MMAP_MEMORY_POOL |
使用 <mmap(2)> 创建内存池。不像前面的映射,它不做后备存储更新 。代价是较低可靠性 。 |
ACE_Sbrk_Memory_Pool |
ACE_SBRK_MEMORY_POOL |
使用 <sbrk(2)> 调用创建内存池。 |
ACE_Shared_Memory_Pool |
ACE_SHARED_MEMORY_POOL |
使用系统 V <shmget(2)> 调用 创建内存池。 |
Memory_Pool |
|
内存可在进程间共享 。 |
ACE_Local_Memory_Pool |
ACE_LOCAL_MEMORY_POOL |
通过 C++ 的 new 和 delete 操作符创建局部 内存池 。该池不能在进程间共享。 |
等待是通过使用 ACE_Thread::join() 调用来完成 的。该方法的参数是你想要主线程与之联接的线程的句柄( ACE_hthread_t )。
4.2 ACE 同步原语
ACE 有若干可用于同步目的的类 。这些类可划分为以下范畴:
- ACE Lock 类属
- ACE Guard 类属
- ACE Condition 类属
- 杂项 ACE Synchronization 类
名字 |
描述 |
ACE_Mutex |
封装互斥机制 (根据平台,可以是 mutex_t 、 pthread_mutex_t 等等)的包装类,用于提供简单而有效的机制来使对共享资源的访问序列化。它与二元信号量( binary semaphore )的功能相类似。可被用于线程和进程间的互斥 。 |
ACE_Thread_Mutex |
可用于替换 ACE_Mutex ,专用于线程同步 。 |
ACE_Process_Mutex |
可用于替换 ACE_Mutex ,专用于进程同步 。 |
ACE_NULL_Mutex |
提供了 ACE_Mutex 接口的“无为”( do-nothing )实现,可在不需要同步时用作替换(单线程使用) 。 |
ACE_RW_Mutex |
封装读者/作者锁的包装类 。它们是分别为读和写进行获取的锁,在没有作者在写的时候,多个读者可以同时进行读取。 |
ACE_RW_Thread_Mutex |
可用于替换 ACE_RW_Mutex ,专用于线程同步。 |
ACE_RW_Process_Mutex |
可用于替换 ACE_RW_Mutex ,专用于进程同步。 |
ACE_Semaphore |
这些类实现计数信号量,在有固定数量的线程可以同时访问一个资源时很有用。在 OS 不提供这种同步机制的情况下,可通过互斥体来进行模拟 。 |
ACE_Thread_Semaphore |
应被用于替换 ACE_Semaphore ,专用于线程同步。 |
ACE_Process_Semaphore |
应被用于替换 ACE_Semaphore ,专用于进程同步。 |
ACE_Token |
提供“递归互斥体” ( recursive mutex ),也就是,当前持有某令牌的线程可以多次重新获取 它,而不会阻塞。 而且,当令牌被释放时,它确保下一个正阻塞并等待此令牌的线程就是下一个被放行的线程。 |
ACE_Null_Token |
令牌接口的“无为”( do-nothing )实现,在你知道不会出现多个线程时使用。 |
ACE_Lock |
定义锁定接口的接口类 。一个纯虚类,如果使用的话,必须承受虚函数调用开销。 |
ACE_Lock_Adapter |
基于模板的适配器 ,允许将前面提到的任意一种锁定机制适配到 ACE_Lock 接口。 |
表 4-1 ACE 锁类属中的类
表 4-1 中描述的类都支持同样的接口。但是,在任何继承层次中,这些类都是互不关联 的。在 ACE 中,锁通常用模板来参数化 ,因为,在大多数情况下,使用虚函数调用的开销都是不可接受的。使用模板使得程序员可获得相当程度的灵活性。他可以在编译时(但不是在运行时)选择他想要使用的的锁定机制的类型。然而,在某些情形中,程序员仍可能需要使用动态绑定和替换( substitution );对于这些情况, ACE 提供了 ACE_Lock 和 ACE_Lock_Adapter 类。
在临界区内完成的工作使用 ACE_Thread_Mutex 互斥体对象进行保护。 该对象由主线程作为参数传给工作者线程。临界区控制是通过在 ACE_Thread_Mutex 对象上发出 acquire() 调用,从而获取互斥体的所有权来完成的。 一旦互斥体被获取,没有其他线程能够再进入这一代码区。临界区控制是通过使用 release() 调用来释放的。 一旦互斥体的所有权被放弃,就会唤醒所有其他在等待的线程。这些线程随即相互竞争,以获得互斥体的所有权。第一个试图获取所有权的线程会进入临界区。
使用示例:
struct Args
{
public:Args(int iterations): mutex_(),iterations_(iterations){}
ACE_Thread_Mutex mutex_;
int iterations_;
};
尽管 ACE_Token 作为所谓的递归锁非常有用,它们实际上是更大的“令牌管理”构架的一部分。该构架允许你维护数据存储中数据的一致性。
ACE 中的守卫用于自动 获取和释放锁 。守卫类的对象定义一个代码块,在其上获取一个锁。在退出此代码块时,锁被自动释放。
名字 |
描述 |
ACE_Guard |
自动在底层锁上调用 acquire() 和 release() 。任何 ACE Lock 类属中的锁都可以作为它的模板参数传入。 |
ACE_Read_Guard |
自动在底层锁上调用 acquire() 和 release() 。 |
ACE_Write_Guard |
自动在底层锁上调用 acquire() 和 release() 。 |
- 获取全局资源(例如,消息队列)的锁(互斥体)。
- 检查条件(例如,消息队列里有空间吗?)。
- 如果条件失败,调用条件变量的 wait() 方法。等待在未来条件变为真。
- 当另一线程在全局资源上执行操作时,它发信号( signal() )给所有其他在此资源上测试条件的线程 (例如,另一线程从消息队列中取出一个消息,然后通过条件变量发送信号,以使阻塞在 wait() 上的线程能够再尝试将它们的消息插入队列)。
- 在醒来之后,重新检查条件现在是否为真。如为真,则在全局资源上执行操作(例如,将消息插入全局消息队列)
while( expression NOT TRUE ) wait on condition variable;
记住条件变量不是用于互斥,而是用于我们所描述的发送信号功能 。
除了上面描述的同步类, ACE 还包括其他一些同步类,比如 ACE_Barrier 和 ACE_Atomic_Op 。
4.3 使用ACE_THREAD_MANAGER 进行线程管理
我们可以使用 ACE_Thread 包装类来创建和销毁线程。但是,该包装类的功能比较有限。 ACE_Thread_Manager 提供了 ACE_Thread 中的功能的超集。特别地,它增加了管理功能,以使启动、取消、挂起和恢复一组相关线程变得更为容易。它用于创建和销毁成组的线程和任务( ACE_Task 是一种比线程更高级的构造,可在 ACE 中用于进行多线程编程。 我们将在后面再来讨论任务)。它还提供了这样的功能:发送信号给一组线程,或是在一组线程上等待, 而不是像我们在前面的例子中所看到的那样,以一种不可移植的方式来调用 join() 。
4.4 线程专有存储(Thread Specific Storage )
对于各个线程来说,可能需要不同的全局或静态数据。可使用线程专有存储来满足此需求。像输入端口这样的结构可放在线程专有存储中,并可像逻辑上的静态或全局变量一样被访问;而实际上它对线程来说是私有的。
传统上,线程专有存储通过让人迷惑的底层操作系统 API 来实现。在 ACE 中, TSS 通过使用 ACE_TSS 模板类来实现。需要成为线程专有的类被传入 ACE_TSS 模板,然后可以使用 C++ 的 -> 操作符来调用它的全部公共方法。
第 5 章 任务和主动对象( Active Object ):并发编程模式 (多线程)
5.1 主动对象
那么到底什么是主动对象呢?传统上,所有的对象都是被动的代码段,对象中的代码是在对它发出方法调用的线程中执行的。也就是,调用线程( calling threads )被“借出”,以执行被动对象的方法。
而主动对象却不一样。这些对象持有它们自己的线程(甚或多个线程),并将这个线程用于执行对它们的任何方法 的调用。 因而,如果你想象一个传统对象,在里面封装了一个线程(或多个线程),你就得到了一个主动对象。
例如,设想对象 “A” 已在你的程序的 main() 函数中被实例化。当你的程序启动时, OS 创建一个线程,以从 main() 函数开始执行。如果你调用对象 A 的任何方法,该线程将“流过”那个方法,并执行其中的代码。一旦执行完成,该线程返回调用该方法的点并继续它的执行。但是,如果 ”A” 是主动对象 ,事情就不是这样了。在这种情况下,主线程不会被主动对象借用。相反,当 ”A” 的方法被调用时,方法的执行发生在主动对象持有的线程中。 另一种思考方法:如果调用的是被动对象的方法(常规对象),调用会阻塞(同步的);而另一方面,如果调用的是主动对象的方法,调用不会阻塞(异步的)。
5.2 ACE_Task
ACE_Task 是 ACE 中的任务或主动对象“处理结构”的基类。 在 ACE 中使用了此类来实现主动对象模式。所有希望成为“主动对象”的对象都必须从此类派生。你也可以把 ACE_TASK 看作是更高级的、更为面向对象的线程类。
当我们在前一章中使用 ACE_Thread 包装时,你一定已经注意到了一些“不好”之处。那一章中的大多数程序都被分解为函数、而不是对象。这是因为 ACE_Thread 包装需要一个全局函数名、或是静态方法作为参数。随后该函数(静态方法)就被用作所派生的线程的“启动点”。这自然就使得程序员要为每个线程写一个函数。如我们已经看到的,这可能会导致非面向对象的程序分解。
相反, ACE_Task 处理的是对象,因而在构造 OO 程序时更便于思考。因此,在大多数情况下,当你需要构建多线程程序时,较好的选择是使用 ACE_Task 的子类。 这样做有若干好处。首要的是刚刚所提到的,这可以产生更好的 OO 软件。其次,你不必操心你的线程入口是否是静态的,因为 ACE_Task 的入口是一个常规的成员函数。 而且,我们会看到 ACE_Task 还包括了一种用于与其他任务进行通信的易于使用的机制。
重申刚才所说的, ACE_Task 可用作:
- 更高级的线程(我们称之为任务)。
- 主动对象模式中的主动对象。
ACE_Task 的结构:每个任务都含有一或多个线程,以及一个底层消息队列 。 各个任务通过这些消息队列进行通信。但是,消息队列并非是程序员需要关注的对象。发送任务可以使用 putq() 调用 来将消息插入到另一任务的消息队列 中。随后接收任务就可以通过使用 getq() 调用 来从它自己的消息队列里将消息提取出来。
要创建任务或主动对象,必须从 ACE_Task 类派生子类 。 在子类派生之后,必须采取以下步骤:
- 实现服务初始化和终止方法: open() 方法应该包含所有专属于任务 的初始化代码。 其中可能包括诸如连接控制块、锁和内存这样的资源。 close() 方法是相应的终止方法。
- 调用启用( Activation )方法: 在主动对象实例化后,你必须通过调用 activate() 启用它。 要在主动对象中创建的线程的数目,以及其他一些参数,被传递给 activate() 方法。 activate() 方法会使 svc() 方法成为所有它生成的线程的启动点。
- 实现服务专有的处理方法: 如上面所提到的,在主动对象被启用后,各个新线程在 svc() 方法中启动(如何区分并调用不同线程) 。 应用开发者必须在子类中定义此方法。
5.3 主动对象模式 ( Active Object Pattern )
主动对象模式用于降低方法执行和方法调用 之间的耦合。 该模式描述了另外一种更为透明的任务间通信方法。
该模式使用 ACE_Task 类作为主动对象。 在这个对象上调用方法时,它就像是常规对象一样。就是说,方法调用是通过同样的 -> 操作符来完成 的,其不同在于这些方法的执行 发生于封装在 ACE_Task 中的线程内 。在使用被动或主动对象进行编程时,客户程序看不到什么区别,或仅仅是很小的区别。对于构架开发者来说,这是非常有用的,因为开发者需要使构架客户与构架的内部结构屏蔽开来。这样构架用户 就不必去担心线程、同步、会合点( rendezvous ),等等。
主动对象模式是 ACE 实现的较为复杂的模式中的一个。该模式有如下参与者:
- 主动对象(基于 ACE_Task )。
- ACE_Activation_Queue 。
- 若干 ACE_Method_Object (主动对象的每个方法都需要有一个方法对象)。
- 若干 ACE_Future 对象(每个要返回结果的方法都需要这样一个对象)。
我们已经看到, ACE_Task 是怎样创建和封装线程的。要使 ACE_Task 成为主动对象,需要完成一些额外的工作:
call() 方法的实现应该将返回的 ACE_Future 对象的内部值 设置为从调用实际的方法实现所获得的结果( 这个实际的方法实现在 ACE_Task 中编写) 。
第 6 章 反应堆( Reactor ):用于事件多路分离和分派的体系结构模式 (事件驱动-异步事件)
6.2 事件处理器
反应堆模式在 ACE 中被实现为 ACE_Reactor 类 ,它提供反应堆构架的功能接口。
如上面所提到的,反应堆将事件处理器对象作为服务提供者使用。 一旦反应堆成功地多路分离和分派了某事件,事件处理器对象就对它进行处理。因此,反应堆会在内部记住当特定类型的事件发生时,应该回调哪一个 事件处理器对象。当应用在反应堆上登记它的处理器对象 ,以处理特定类型的事件 时,反应堆会创建这种事件 和相应的事件处理器 的关联。
因为反应堆需要记录哪一个事件处理器将被回调,它需要知道所有事件处理器对象的类型。这是通过替换模式( Substitution Pattern )的帮助来实现的(或者换句话说,通过“是……类型”( is a type of )变种继承)。该构架提供名为 ACE_Event_Handler 的抽象接口类 ,所有应用特有的事件处理器 都必须 由此派生 (这使得应用特有的处理器都具有相同的类型,即 ACE_Event_Handler ,所以它们可以相互替换)。
ACE_Event_Handler 类拥有若干不同的“ handle ”(处理)方法,每个处理方法被用于处理不同种类的事件。 当应用程序员对特定事件感兴趣时,他就对 ACE_Event_Handler 类进行子类化 ,并实现他感兴趣的处理方法。如上面所提到的,随后他就在反应堆上为特定事件“登记”他的事件处理器类 。于是反应堆就会保证在此事件发生时,自动回调在适当的事件处理器对象中的适当的 ”handle” 方法 。
使用 ACE_Reactor 基本上有三个步骤:
- 创建 ACE_Event_Handler 的子类,并在其中实现适当的“ handle_ ”方法, 以处理你想要此事件处理器为之服务的事件类型。(参看表 6-1 来确定你需要实现哪一个“ handle_” 方法。注意你可以使用同一个事件处理器对象处理多种类型的事件,因而可以重载多于一个的 “handle_” 方法。)
- 通过调用反应堆对象的 register_handler() ,将你的事件处理器登记到反应堆。
- 在事件发生时,反应堆将自动回调 相应的事件处理器对象的适当的“ handle_” 方法。
ACE_Event_Handler 中的处理方法 |
在子类中重载,所处理事件的类型 :
|
handle_signal() |
信号。当任何在反应堆上登记的信号发生 时,反应堆自动回调该方法。 |
handle_input() |
来自 I/O 设备的输入。当 I/O 句柄(比如 UNIX 中的文件描述符)上的输入可用 时,反应堆自动回调该方法。 |
handle_exception() |
异常事件。当已在反应堆上登记的异常事件发生 时(例如,如果收到 SIGURG (紧急信号)),反应堆自动回调该方法。 |
handle_timeout() |
定时器。当任何已登记的定时器超时 的时候,反应堆自动回调该方法。 |
handle_output() |
I/O 设备输出。当 I/O 设备的输出队列有可用空间 时,反应堆自动回调该方法。 |
表 6-1 ACE_Event_Handler 中的处理方法及其对应事件
6.3 通过反应堆进行事件处理
#include "ace/SOCK_Acceptor.h"
typedef ACE_SOCK_Acceptor Acceptor;
class My_Input_Handler: public ACE_Event_Handler
ACE_DEBUG((LM_DEBUG,"Constructor/n"));
//Called back to handle any input received
ACE_DEBUG((LM_DEBUG,"%s/n",data));
// do something with the input received.
//keep yourself registered with the reactor
//Used by the reactor to determine the underlying handle
return this->peer_.get_handle();
//return this->peer_i().get_handle();
//Returns a reference to the underlying stream.
ACE_SOCK_Stream peer_; //public
class My_Accept_Handler: public ACE_Event_Handler
My_Accept_Handler(ACE_Addr &addr)
//Open the peer_acceptor so it starts to ”listen ”
//Overload the handle input method
int handle_input(ACE_HANDLE handle)
//Client has requested connection to server.
//Create a handler to handle the connection
My_Input_Handler *eh = new My_Input_Handler();
//Accept the connection "into" the Event Handler
if (this->peer_acceptor.accept (eh->peer_, // stream
1) ==-1) //restart if interrupted
ACE_DEBUG((LM_ERROR,"Error in connection/n"));
ACE_DEBUG((LM_DEBUG,"Connection established/n"));
//Register the input event handler for reading
ACE_Reactor::instance()->register_handler(eh,ACE_Event_Handler::READ_MASK);
//Unregister as the acceptor is not expecting new clients
//Used by the reactor to determine the underlying handle
ACE_HANDLE get_handle(void) const
return this->peer_acceptor.get_handle();
int main(int argc, char * argv[])
//Create an address on which to receive connections
//Create the Accept Handler which automatically begins to “listen ”
//for client requests for connections
My_Accept_Handler *eh=new My_Accept_Handler(addr);
//Register the reactor to call back when incoming client connects
ACE_Reactor::instance()->register_handler(eh,ACE_Event_Handler::ACCEPT_MASK);
ACE_Reactor::instance()->handle_events();
掩码 |
回调方法 |
何时 |
和……一起使用 |
ACE_Event_Handler::READ_MASK |
handle_input() |
在句柄 上有数据可读时。 |
register_handler() |
ACE_Event_Handler::WRITE_MASK |
handle_output() |
在 I/O 设备 输出缓冲区上有可用空间、并且新数据可以发送给它时。 |
register_handler() |
ACE_Event_Handler::TIMER_MASK |
handle_close() |
传给 handle_close() 以指示调用它的原因是超时。 |
接受器和连接器的 handle_timeout 方法。 反应堆不 使用此掩码。 |
ACE_Event_Handler::ACCEPT_MASK |
handle_input() |
在 OS 内部的侦听队列上收到了客户的新连接请求 时。 |
register_handler() |
ACE_Event_Handler::CONNECT_MASK |
handle_input() |
在连接已经建立 时。 |
register_handler() |
ACE_Event_Handler::DONT_CALL |
None. |
在反应堆的 remove_handler() 被调用时保证事件处理器的 handle_close() 方法不 被调用 。 |
remove_handler() |
6.4 定时器( Timer )
反应堆还包括了调度定时器的方法,它们在超时的时候回调适当的事件处理器的 handle_timeout() 方法。为调度这样的定时器,反应堆拥有一个 schedule_timer() 方法。该方法接收事件处理器(该事件处理器的 handle_timeout() 方法将会被回调)、以及以 ACE_Time_value 对象形式出现的延迟作为参数。此外,还可以指定时间间隔,使定时器在它超时后自动被复位。
反应堆在内部维护 ACE_Timer_Queue , 它以定时器要被调度的顺序对它们进行维护。实际使用的用于保存定时器的数据结构可以通过反应堆的 set_timer_queue() 方法进行改变 。反应堆有若干不同的定时器结构 可用,包括定时器轮( timer wheel )、定时器堆( timer heap )和哈希式定时器轮( hashed timer wheel )。 这些内容将在后面的部分详细讨论。
ACE_Time_Value 是封装底层 OS 平台的日期和时间结构的包装类 。 它基于在大多数 UNIX 操作系统上都可用的 timeval 结构 ;该结构存储以秒和微秒计算的绝对时间。
其他的 OS 平台,比如 POSIX 和 Win32 ,使用略有不同的表示方法。该类封装这些不同,并提供了可移植的 C++ 接口。
不同的环境可能需要不同的调度和取消定时器的方法。在下面的任一条件为真时,实现定时器的算法的性能就会成为一个问题 :
ACE 允许用户从若干在 ACE 中已存在的定时器中进行选择,或是根据为定时器定义的接口开发他们自己的定时器 。 表 6-3 详细列出了 ACE 中可用的各种定时器:
定时器 |
数据结构描述 |
性能 |
ACE_Timer_Heap |
定时器存储在优先级队列 的堆实现中。 |
schedule_timer() 的开销 =O(lg n) cancel_timer() 的开销 =O(lg n) 查找当前定时器的开销 =O(1) |
ACE_Timer_List |
定时器存储在双向链表 中。 |
schedule_timer() 的开销 =O(n) cancel_timer() 的开销 =O(1) 查找当前定时器的开销 =O(1) |
ACE_Timer_Hash |
在这里使用的这种结构是定时器轮算法的变种。性能高度依赖于所用的哈希函数 。 |
schedule_timer() 的开销 = 最坏 =O(n) 最佳 =O(1) cancel_timer() 的开销 =O(1) 查找当前定时器的开销 =O(1) |
ACE_Timer_Wheel |
定时器存储在“数组指针 ” (pointers to arrays) 的数组中。每个被指向的数组都已排序。 |
schedule_timer() 的开销 = 最坏 =O(n) cancel_timer() 的开销 =O(1) 查找当前定时器的开销 =O(1) |
6.5 处理信号 ( Signal )
如我们在例 6-1 中所看到的,反应堆含有进行信号处理的方法。处理信号的事件处理器应重载 handle_signal() 方法,因为该方法将在信号发生时被回调。要为信号登记处理器,可以使用多个 register_handler() 方法中的一个 ,就如同例 6-1 中所演示的那样。如果对特定信号不再感兴趣,通过调用 remove_handler() ,处理器可以被拆除,并恢复为先前安装的信号处理器。 反应堆在内部使用 sigaction() 系统调用来设置和恢复信号处理器。通过使用 ACE_Sig_Handlers 类和与其相关联的方法,无需反应堆也可以进行信号处理。
使用反应堆进行信号处理和使用 ACE_Sig_Handlers 类的重要区别是基于反应堆的机制只允许应用给每个信号关联一个事件处理器,而 ACE_Sig_Handlers 类允许在信号发生时,回调多个事件处理器。
6.6 使用通知( Notification )
反应堆不仅可以在系统事件发生 时发出回调,也可以在用户定义的事件发生 时回调处理器。 这是通过反应堆的“通知”接口来完成的;该接口由两个方法组成: notify() 和 max_notify_iterations() 。
通过使用 notify() 方法,可以明确地指示反应堆对特定的事件处理器对象 发出回调。 在反应堆与消息队列、或是协作任务协同使用时,这是十分有用的。 可在 ASX 构架组件与反应堆一起使用时找到这种用法的一些好例子。
max_notify_iterations() 方法通知反应堆,每次只完成指定次数的“迭代”( iterations )。 也就是说,在一次 handle_events() 调用中只处理指定数目的“通知”。 因而如果使用 max_notify_iterations() 将迭代的次数设置为 20 ,而又有 25 个通知同时到达, handle_events() 方法一次将只处理这些通知中的 20 个。剩下的五个通知将在 handle_events() 下一次在事件循环中被调用时再处理。
事件处理循环 中值得注意的一个主要区别是,程序传递给 handle_events() 一个 ACE_Time_Value 。 如果在此时间内没有事件发生, handle_events() 方法就会结束 。在 handle_events() 结束后, perform_notification() 被调用 ,它使用反应堆的 notify() 方法来请求反应堆通知处理器 (它是在事件发生时被作为参数传入的)。随后反应堆就使用所收到的掩码来执行对处理器的适当“ handle ”方法 的调用。 在此例中,通过传递 ACE_Event_Handler::READ_MASK ,我们使用 notify() 来通知我们的事件处理器有输入 ,从而使得反应堆回调该处理器的 handle_input() 方法。因为我们已将 max_notify_iterations 设为 5 ,所以在一次 handle_events() 调用过程中反应堆实际上只会发出 5 个通知。
第 7 章 接受器( Acceptor )和连接器( Connector ):连接建立模式
接受器 / 连接器模式设计用于降低连接建立与连接建立后所执行的服务之间的耦合。 因为该模式降低了服务和连接建立方法之间的耦合,非常容易改动其中一个,而不影响另外一个,从而也就可以复用以前编写的连接建立机制和服务例程的代码。
7.1 接受器模式
在 ACE 中,接收器模式借助名为 ACE_Acceptor 的“工厂”( Factory ) 实现。工厂(通常)是用于对助手对象的实例化过程 进行抽象的类。 在面向对象设计中,复杂的类常常会将特定功能委托给助手类。复杂的类对助手的创建和委托必须很灵活。这种灵活性是在工厂的帮助下获得的。工厂允许一个对象通过改变它所委托的对象 来改变它的底层策略,而工厂提供给应用的接口却是一样的 ,这样,可能根本就无需对客户代码进行改动(有关工厂的更多信息,请阅读“设计模式”中的参考文献)。
ACE_Acceptor 工厂允许应用开发者改变“助手”对象,以用于:
- 被动连接建立
- 连接建立后的处理
同样地, ACE_Connector 工厂允许应用开发者改变“助手”对象,以用于:
- 主动连接建立
- 连接建立后的处理
ACE_Acceptor 被实现为模板容器 ,通过两个类作为实参来进行实例化 。第一个参数实现特定的服务(类型为ACE_Event_Handler 。 因为它被用于处理I/O 事件,所以必须来自事件处理类层次),应用在建立连接后执行该服务 ;第二个参数是“具体的”接受器 (可以是在IPC_SAP 一章中讨论的各种变种)。
特别要注意的是ACE_Acceptor 工厂和底层所用的具体接受器是非常不同的。具体接受器可完全独立于ACE_Acceptor 工厂使用 ,而无需涉及我们在这里讨论的接受器模式(独立使用接受器已在IPC_SAP 一章中讨论和演示)。ACE_Acceptor 工厂内在于接受器模式,并且不能在没有底层具体接受器的情况下使用。ACE_Acceptor 使用 底层的具体接受器来建立连接。 如我们已看到的,有若干ACE 的类可被用作ACE_Acceptor 工厂模板的第二个参数(也就是,具体接受器类)。但是服务处理类必须由应用开发者来实现,而且其类型必须是ACE_Event_Handler 。 ACE_Acceptor 工厂可以这样来实例化:
typedef ACE_Acceptor<My_Service_Handler,ACE_SOCK_ACCEPTOR> MyAcceptor;
这里,名为My_Service_Handler 的事件处理器和具体接受器ACE_SOCK_ACCEPTOR 被传给MyAcceptor 。ACE_SOCK_ACCEPTOR 是基于BSD socket 流家族的TCP 接受器(各种可传给接受器工厂的不同类型的接受器,见表7-1 和IPC 一章)。请再次注意,在使用接受器模式时,我们总是处理两个接受器:名为ACE_Acceptor 的工厂接受器,和ACE 中的某种具体接受器,比如ACE_SOCK_ACCEPTOR (你可以创建自定义的具体接受器来取代ACE_SOCK_ACCEPTOR ,但你将很可能无需改变ACE_Acceptor 工厂类中的任何东西)。
重要提示 :ACE_SOCK_ACCEPTOR 实际上是一个宏,其定义为:
#define ACE_SOCK_ACCEPTOR ACE_SOCK_Acceptor, ACE_INET_Addr
我们认为这个宏的使用是必要的,因为在类中的typedefs 在某些平台上无法工作。如果不是这样的话,ACE_Acceptor 就可以这样来实例化:
typedef ACE_Acceptor<My_Service_Handler,ACE_SOCK_Acceptor>MyAcceptor;
- 具体接受器 :它含有建立连接的特定策略,连接与底层的传输协议机制系在一起。 下面是在 ACE 中的各种具体接受器的例子: ACE_SOCK_ACCEPTOR (使用 TCP 来建立连接)、 ACE_LSOCK_ACCEPTOR (使用 UNIX 域 socket 来建立连接),等等。
- 具体服务处理器 :由应用开发者编写,它的 open() 方法在连接建立后被自动回调。 接受器构架假定 服务处理类的类型是 ACE_Event_Handler ,这是 ACE 定义的 接口类 (该类已在反应堆一章中详细讨论过)。另一个 特别为接受器和连接器模式的服务处理而创建的类是 ACE_Svc_Handler 。 该类不仅基于 ACE_Event_Handler 接口(这是使用反应堆所必需的),同时还基于在 ASX 流构架中使用的 ACE_Task 类。 ACE_Task 类提供的功能有:创建分离的线程、使用消息队列来存储到来的数据消息、并发地处理它们,以及其他一些有用的功能。如果与接受器模式一起使用的具体服务处理器派生自 ACE_Svc_Handler 、而不是 ACE_Event_Handler ,它就可以获得这些额外的功能。对 ACE_Svc_Handler 中的额外功能的使用,在这一章的高级课程里详细讨论。在下面的讨论中,我们将使用 ACE_Svc_Handler 作为我们的事件处理器。在简单的 ACE_Event_Handler 和 ACE_Svc_Handler 类之间的重要区别是,后者拥有一个底层通信流组件。这个流在 ACE_Svc_Handler 模板被实例化的时候设置。而在使用 ACE_Event_Handler 的情况下,我们必须自己增加 I/O 通信端点(也就是,流对象),作为事件处理器的私有数据成员 。因而,在这样的情况下, 应用开发者应该将他的服务处理器创建为 ACE_Svc_Handler 类的子类 ,并首先实现将被构架自动回调的 open() 方法 。此外,因为 ACE_Svc_Handler 是一个模板 ,通信流组件和锁定机制 是作为模板参数被传入的。
- 反应堆 : 与 ACE_Acceptor 协同使用 。如我们将看到的, 在实例化接受器 后,我们启动反应堆的事件处理循环。 反应堆,如先前所解释的,是一个事件分派类;而在此情况下,它被接受器 用于将连接建立事件分派到适当的服务处理例程。
接受器类型 |
所用地址 |
所用流 |
具体接受器 |
TCP 流接受器 |
ACE_INET_Addr |
ACE_SOCK_STREAM |
ACE_SOCK_ACCEPTOR |
UNIX 域本地流 socket 接受器 |
ACE_UNIX_Addr |
ACE_LSOCK_STREAM |
ACE_LSOCK_ACCEPTOR |
管道作为底层通信机制 |
ACE_SPIPE_Addr |
ACE_SPIPE_STREAM |
ACE_SPIPE_ACCEPTOR |
7.2 连接器
连接器 与接受器非常类似。它也是一个工厂 ,但却是用于主动地 连接远程主机。在连接建立后,它将自动回调适当的服务处理对象的open() 方法。 连接器通常被用在你本来会使用BSD connect() 调用的地方。在ACE 中,连接器,就如同接受器,被实现为名为ACE_Connector 的模板容器类 。如先前所提到的,它需要两个参数,第一个是事件处理器类,它在连接建立时被调用 ;第二个是“具体的”连接器类。
你必须 注意,底层的具体连接器和ACE_Connector 工厂是非常不一样的。ACE_Connector 工厂使用 底层的具体连接器来建立连接。随后ACE_Connector 工厂使用适当的事件或服务处理例程(通过模板参数传入)来在具体的连接器建立起连接之后处理新连接。 如我们在IPC 一章中看到的,没有ACE_Connector 工厂,也可以使用这个具体的连接器。但是,没有具体的连接器类,就会无法使用ACE_Connector 工厂(因为要由具体的连接器类来实际处理连接建立)。
下面是对ACE_Connector 类进行实例化的一个例子:
typedef ACE_Connector<My_Svc_Handler,ACE_SOCK_CONNECTOR> MyConnector;
这个例子中的第二个参数是具体连接器类ACE_SOCK_CONNECTOR 。连接器和接受器模式一样,在内部使用反应堆来在连接建立后回调服务处理器的open() 方法。我们可以复用我们为前面的接受器例子所写的服务处理例程。
7.3 高级课程
下面的部分更为详细地解释接受器和连接器模式实际上是如何工作的。如果你想要调谐服务处理和连接建立策略(其中包括调谐底层具体连接器将要使用的服务处理例程的创建和并发策略,以及连接建立策略),对该模式的进一步了解就是必要的。此外,还有一部分内容解释怎样使用通过ACE_Svc_Handler 类自动获得的高级特性。最后,我们说明怎样与接受器和连接器模式一起使用简单的轻量级ACE_Event_Handler 。
ACE_Task 被设计为与ASX 流构架一起使用 ;ASX 基于UNIX 系统V 中的流机制。在设计上ASX 与Larry Peterson 构建的X-kernel 协议工具非常类似。
public ACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>
// = Active object activation method.
virtual int activate (long flags = THR_NEW_LWP,
long priority = ACE_DEFAULT_THREAD_PRIORITY,
ACE_hthread_t thread_handles[] = 0,
ACE_thread_t thread_names[] = 0);
第一个参数flags 描述将要创建的线程所希望具有的属性 。在线程一章里有详细描述。可用的标志有:
THR_CANCEL_DISABLE, THR_CANCEL_ENABLE, THR_CANCEL_DEFERRED,
THR_CANCEL_ASYNCHRONOUS, THR_BOUND, THR_NEW_LWP, THR_DETACHED,
THR_SUSPENDED, THR_DAEMON, THR_JOINABLE, THR_SCHED_FIFO,
THR_SCHED_RR, THR_SCHED_DEFAULT
7.4 接受器和连接器模式 工作原理
接受器和连接器工厂(也就是 ACE_Connector 和 ACE_Acceptor )有着非常类似的运行结构。它们的工作可大致划分为三个阶段:
- 端点或连接初始化阶段
- 服务初始化阶段
- 服务处理阶段
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
在有连接请求在指定的地址和端口上到来时,反应堆自动回调 ACE_Acceptor 工厂的 handle_input() 方法。
- make_svc_handler() :创建服务处理器。
- accept_svc_handler() :将连接接受进前一步骤创建的服务处理器。
- activate_svc_handler() :启动这个新服务处理器。
这些方法都可以被重新编写 ,从而灵活地决定这些操作怎样来实际执行。
应用发出的 connect() 方法 与接受器工厂中的 handle_input() 相类似,也就是,它是一个“模板方法”。
在我们的这个案例中,模板方法 connect() 定义下面一些可被重定义的步骤 :
- make_svc_handler() :创建服务处理器。
- connect_svc_handler() :将连接接受进前一步骤创建的服务处理器。
- activate_svc_handler() :启动这个新服务处理器。
每一方法都可以被重新编写,从而灵活地决定这些操作怎样来实际执行。
与 accept_svc_handler() 情况一样, connect_svc_handler() 是一个“桥接”方法,可进行重载以提供变化的功能。
7.5 调谐接受器和连接器策略
如上面所提到的,因为使用了可以重载的桥接方法,很容易对接受器和连接器进行调谐。桥接方法允许调谐:
- 服务处理器的创建策略: 通过重载接受器或连接器的 make_svc_handler() 方法来实现。例如,这可以意味着复用已有的服务处理器 ,或使用某种复杂的方法来获取服务处理器,如上面所讨论的那样。
- 连接策略: 连接创建策略可通过重载 connect_svc_handler() 或 accept_svc_handler() 方法来改变。
- 服务处理器的并发策略: 服务处理器的并发策略可通过重载 activate_svc_handler() 方法来改变。例如,服务处理器可以在另外的进程中创建。
如上所示,调谐是通过重载 ACE_Acceptor 或 ACE_Connector 类的桥接方法来完成的。 ACE 的设计使得程序员很容易完成这样的重载和调谐。
7.5.1 ACE_Strategy_Connector 和 ACE_Strategy_Acceptor 类
在 ACE 中已有若干具体的策略类可用于“调谐”策略接受器和连接器。当类被实例化时,它们作为参数被传入策略接受器或连接器。表 7-2 显示了可用于调谐策略接受器和连接器类的一些类。
需要修改 |
具体策略类 |
描述 |
创建策略 ( 重定义make_svc_handler()) |
ACE_NOOP_Creation_Strategy |
这个具体策略并不 实例化服务处理器,而只是一个空操作。 |
ACE_Singleton_Strategy |
保证服务处理器被创建为单体。也就是,所有连接将有效地使用同一个服务处理例程。 |
|
ACE_DLL_Strategy |
通过从动态链接库中动态链接服务处理器来对它进行创建。 |
|
连接策略 ( 重定义connect_svc_handler()) |
ACE_Cached_Connect_Strategy |
检查是否有已经连接到特定的远地地址的服务处理器没有在被使用。如果有这样一个服务处理器,就对它进行复用 。 |
并发策略 ( 重定义activate_svc_handler()) |
ACE_NOOP_Concurrency_Strategy |
一个“无为”(do-nothing )的并发策略。它甚至不 调用服务处理器的open() 方法。 |
ACE_Process_Strategy |
在另外的进程中创建服务处理器 ,并调用它的open() 方法。 |
|
ACE_Reactive_Strategy |
先在反应堆上登记服务处理器,然后调用它的open() 方法。 |
|
ACE_Thread_Strategy |
先调用服务处理器的open() 方法,然后调用它的activate() 方法,以让另外的线程来启动服务处理器的svc() 方法。 |
7.5.1.2 使用 ACE_Cached_Connect_Strategy 进行连接缓存
第 8 章 服务配置器 (Service Configurator) :用于服务动态配置的模式
服务配置器模式可以完成所有这些任务。它使服务的实现 与配置 去耦合 。无需关闭服务器,就可以在应用中增加新服务和移除旧服务。在大多数情况下,提供服务的服务器都被实现为看守( daemon )进程。
8.1 构架组件
ACE 中的服务配置器由以下组件组成:
- 名为 ACE_Service_Object 的抽象类。 应用开发者必须从它派生出子类,以创建他自己的应用特有的具体服务对象( Service Object )。
- 应用特有的具体服务对象。
- 服务仓库 ACE_Service_Repository 。 它记录服务器所运行的和所知道的服务。
- ACE_Service_Config 。它是整个服务配置器框架的应用开发接口 。
- 服务配置文件。该文件含有所有服务对象的配置信息。其缺省的名字是 svc.conf 。 当你的应用对 ACE_Service_Config 发出 open() 调用时,服务配置器框架会读取并处理你写在此文件中的所有配置信息,随后相应地配置应用。
ACE_Service_Object 包括了一些由框架调用的方法,用于服务要启动( init() )、停止( fini() )、挂起( suspend() )或是恢复( resume() )时。 ACE_Service_Object 派生自 ACE_Shared_Object 和 ACE_Event_Handler 。 ACE_Shared_Object 在应用想要使用操作系统的动态链接机制来进行加载 时被用作抽象基类。 ACE_Event_Handler 已在对反应堆的讨论中进行了介绍。当开发者想要他的类响应来自反应堆的事件时,他就从 ACE_Event_Handler 派生他的子类。
为什么服务对象要从 ACE_Event_Handler 继承?用户发起重配置的一种方法是生成一个信号;当这样的信号事件发生时,反应堆被用于处理信号,并向 ACE_Service_Config 发出重配置请求。除此而外,软件的重配置也可能在某事件产生后发生。因而所有的服务对象都被构造为能对事件进行处理。
服务配置文件有它自己的简单脚本,用于描述你想要服务怎样启动和运行。你可以定义你是想要增加新服务,还是挂起、恢复或移除应用中现有的服务。另外还可以给服务发送参数。服务配置器还允许进行基于 ACE 的流 ( stream )的重配置。我们将在讨论了 ACE 流构架之后再来更多地讨论这一点。
8.2 定义配置文件
服务配置文件指定在应用中哪些服务要被加载和启动。此外,你可以指定哪些服务要被停止、挂起或恢复。还可以发送参数给你的服务对象的 init() 方法。
8.2.1 启动服务
服务可以被静态或动态地启动。 如果服务要动态启动 ,服务配置器实际上会从共享对象库(也就是,动态链接库)中加载服务对象。为此,服务配置器需要知道哪个库含有此对象,并且还需要知道对象在该库中的名字。 因而,在你的代码文件中你必须通过你需要记住的名字来实例化服务对象。于是动态服务会这样被配置:
dynamic service_name type_of_service * location_of_shared_lib:name_of_object “parameters ”
而静态服务这样被初始化:
static service_name “parameters_send_to_service_object ”
8.2.2 挂起或恢复服务
如刚才所提到的,你在启动服务时分配给它一个名字。这个名字随后被用于挂起或恢复该服务。于是要挂起服务,你所要做的就是在 svc.conf 文件中指定:
suspend service_name
这使得服务对象中的 suspend() 方法被调用。随后你的服务对象就应该挂起它自己(基于特定服务不同的“挂起”含义)。
如果你想要恢复这个服务,你所要做的就是在 svc.conf 文件中指定:
resume service_name
这使得服务对象中的 resume() 方法被调用。随后你的服务对象就应该恢复它自己(基于特定服务不同的“恢复”含义。)
8.2.3 停止服务
停止并移除服务(如果服务是动态加载的)同样是很简单的操作,可以通过在你的配置文件中指定以下指令来完成:
remove service_name
这使得服务配置器调用你的应用的 fini() 方法。该方法应该使此服务停止。服务配置器自己会负责将动态对象从服务器的地址空间里解除链接。
8.3 编写服务
为服务配置器编写你自己的服务相对比较简单。你可以让这个服务做任何你想做的事情。唯一的约束是它应该是 ACE_Service_Object 的子类。所以它必须实现 init() 和 fini() 方法。 在 ACE_Service_Config 被打开( open() )时,它读取配置文件(也就是 svc.conf )并根据这个文件来对服务进行初始化。一旦服务被加载,它会调用该服务对象的 init() 方法。 类似地,如果配置文件要求移除服务, fini() 方法就会被调用。 这些方法负责分配和销毁服务所需的任何资源 ,比如内存、连接、线程等等。在 svc.conf 文件中指定的参数通过服务对象的 init () 方法来传入。
下面的例子演示一个派生自 ACE_Task_Base 的服务。 ACE_Task_Base 类含有 activate() 方法,用于在对象里创建线程。(在“任务和主动对象”一章中讨论过的 ACE_Task 派生自 ACE_Task_Base ,并包括了用于通信目的的消息队列。因为我们不需要我们的服务与其它任务通信,我们仅仅使用 ACE_Task_Base 来帮助我们完成工作。)更多详细信息,请阅读“任务和主动对象”一章。该服务是一个“无为”( do-nothing )的服务,一旦启动,它只是周期性地广播当天的时间。
相应的实现如下所述:在时间服务接收到 init() 调用时,它在任务中启用( activate() )一个线程。这将会创建一个新线程,其入口为 svc() 方法。在 svc() 方法中,该线程将会进行循环,直到它看到 canceled_ 标志被设置为止。此标志在服务配置构架调用 fini() 时设置。但是,在 fini() 方法返回底层的服务配置框架之前 ,它必须确定 在底层的线程已经终止。 因为服务配置器将要实际地卸载含有 TimeService 的共享库,从而将 TimeService 对象从应用进程中删除。如果在此之前 线程并未终止,它将会对已经被服务配置器“蒸发”的代码发出调用!我们当然不需要这个。为了确保线程在服务配置器“蒸发” TimeService 对象之前终止 ,程序使用了条件变量。(要更多地了解怎样使用条件变量,请阅读有关线程的章节)。
下面是一个简单的、只是用于启用时间服务的配置文件。可以去掉注释 # 号来挂起、恢复和移除服务。
例8-1c
# To configure different services, simply uncomment the appropriate
#lines in this file!
#resume TimeService
#suspend TimeService
#remove TimeService
#set to dynamically configure the TimeService object and do so without
#sending any parameters to its init method
dynamic TimeService Service_Object * ./Server:time_service ""
最后,下面是启动服务配置器的代码段。这些代码还设置了一个信号处理器对象,用于发起重配置。该信号处理器已被设置成响应 SIGWINCH 信号(在窗口发生变化时产生的信号)。在启动服务配置器之后,应用进入一个反应式循环,等待 SIGWINCH 信号事件发生。一旦事件发生,就会回调事件处理器,由它调用 ACE_Service_Config 的 reconfigure() 方法。如先前所讲述的,在此调用发生时,服务配置器重新读取配置文件,并处理用户放在其中的任何新指令。例如,在动态启动 TimeService 后,在这个例子中你可以改变 svc.conf 文件,只留下一个挂起命令在里面。当配置器读取它时,它将调用 TimeService 的挂起方法,从而使它挂起它的底层线程。类似地,如果稍后你又改变了 svc.conf ,要求恢复服务,配置器就会调用 TimeService::resume() 方法,从而恢复先前被挂起的线程。
8.4 使用服务管理器
ACE_Service_Manager 是可用于对服务配置器进行远程管理的服务 。它目前可以接受两种类型的请求。其一,你可以向它发送“ help ”消息,列出当前被加载进应用的所有服务。其二,你可以向服务管理器发送“ reconfigure ”消息,从而使得服务配置器重新配置它自己。
9.1 消息块
在 ACE 中,消息作为消息块( Message Block )被放入消息队列中 。消息块包装正被存储的实际消息数据,并提供若干数据插入和处理操作。每个消息块“包含”一个头和一个数据块。 注意在这里“包含”是在宽松的意义上使用的。消息块可以不对与数据块( Data Block )或是消息头( Message Header )相关联的内存进行管理(尽管你可以让消息块进行这样的管理)。它仅仅持有指向两者的指针。所以包含只是逻辑上的。数据块持有指向实际的数据缓冲区的指针 。如图 9-1 所示,这样的设计带来了多个消息块之间的数据的灵活共享。注意在图中两个消息块共享一个数据块。这样,无需带来数据拷贝开销,就可以将同一数据放入不同的队列中。
消息块类名为 ACE_Message_Block ,而数据块类名为 ACE_Data_Block 。 ACE_Message_Block 的构造器是实际创建消息块和数据块的方便办法。
9.1.1.1 ACE_Message_Block 分配和管理数据内存
ACE_Message_Block (size_t size,
ACE_Message_Type type = MB_DATA,
ACE_Allocator *allocator_strategy = 0,
ACE_Lock *locking_strategy = 0,
const ACE_Time_Value & execution_time = ACE_Time_Value::zero,
const ACE_Time_Value & deadline_time = ACE_Time_Value::max_time);
- 要与消息块相关联的数据缓冲区的大小 。注意消息块的大小是 size ,但长度将为0,直到 wr_ptr 被设置为止。 这将在后面进一步解释。
- 消息的类型 。(在 ACE_Message_Type 枚举中有若干类型可用,其中包括缺省的数据消息)。
- 指向“片段链”( fragment chain )中的下一个消息块的指针 。消息块可以实际地链接在一起来形成链。随后链可被放入消息队列中,就好像它是单个数据块一样。该参数缺省为0,意味着此块不使用链。
- 指向要存储在此消息块中的数据缓冲区的指针 。如果该参数的值为零,就会创建缓冲区(大小由第一个参数指定),并由该消息块进行管理 。当消息块被删除时,相应的数据缓冲区也被删除。但是,如果在此参数中指定了数据缓冲区,也就是,参数不为空,当消息块被销毁时它就不会 删除数据缓冲区。 这是一个重要特性,必须牢牢记住。
- 用于分配数据缓存(如果需要)的 allocator_strategy ,在第四个参数为空时使用(如上面所解释的)。任何 ACE_Allocator 的子类都可被用作这一参数。(关于 ACE_Allocator 的更多信息,参见“内存管理”一章)。
- 如果 locking_strategy 不为零,它就将用于保护访问共享状态(例如,引用计数)的代码区,以避免竞争状态。
- 这个参数以及后面两个参数用于 ACE 中的实时消息队列的调度,目前应保留它们的缺省值。
如果你正在使用 ACE_Message_Block ,你并不一定要让它来为你分配内存。消息块的构造器允许你:
下面的例子演示怎样将指向消息数据的指针传给消息块,以及 ACE_Message_Block 怎样创建和管理底层的 ACE_Data_Block 。
//Create a message block to hold the data
ACE_Message_Block *mb = new ACE_Message_Block (data, // data that is stored
blocksize); //size of the block that
除了构造器, ACE_Message_Block 还提供若干方法来直接在消息块中插入数据。另外还有一些方法可用来操作已经在消息块中的数据。
9.1.2.1 拷贝与复制(Copying and Duplicating )
可以使用 ACE_Message_Block 的 copy() 方法来将数据拷贝进消息块 。
int copy(const char *buf, size_t n);
9.2 ACE 的消息队列
如先前所提到的, ACE 有若干不同类型的消息队列,它们大体上可划分为两种范畴:静态的和动态的。静态队列是一种通用的消息队列( ACE_Message_Queue ),而动态消息队列( ACE_Dynamic_Message_Queue )是实时消息队列。 这两种消息队列的主要区别是:静态队列中的消息具有静态的优先级,也就是,一旦优先级被设定就不会再改变; 而另一方面,在动态消息队列中,基于诸如执行时间和最终期限等参数,消息的优先级可以动态地改变 。
例子由一个 Qtest 类组成,它通过 ACE_NULL_SYNCH 锁定来实例化缺省大小 的消息队列。锁(互斥体和条件变量)被消息队列用来:
- 保证由消息块维护的引用计数的安全,防止在有多个线程访问时的竞争状态。
- “唤醒”所有因为消息队列空或满而休眠的线程。
在此例中,因为只有一个线程,消息队列的模板同步参数被设置为空( ACE_NULL_SYNCH ,意味着使用 ACE_Null_Mutex 和 ACE_Null_Condition )。随后 Qtest 的 enq_msgs() 方法被调用,它进入循环,创建消息、并将其放入消息队列中。消息数据的大小作为参数传给 ACE_Message_Block 的构造器。使用该构造器使得内存被自动地管理(也就是,内存将在消息块被删除时,即 release() 时被释放) 。 wr_ptr 随后被获取(使用 wr_ptr() 访问方法),且数据被拷贝进消息块。在此之后, wr_ptr 向前增长。然后使用消息队列的 enqueue_prio() 方法来实际地将消息块放入底层消息队列中。
在 no_msgs_ 个消息块被创建、初始化和插入消息队列后, enq_msgs() 调用 deq_msgs() 方法。该方法使用 ACE_Message_Queue 的 dequeue_head() 方法来使消息队列中的每个消息出队。在消息出队后,就显示它的数据,然后再释放消息。
9.3 水位标
水位标用于在消息队列中指示何时在其中的数据已过多(消息队列到达了高水位标),或何时在其中的数据的数量不足(消息队列到达了低水位标)。两种水位标都用于流量控制 。 例如, low_water_mark 可用于避免像 TCP 中的“傻窗口综合症”( silly window syndrome )那样的情况,而 high_water_mark 可用于“阻止“或减缓数据的发送或生产。
ACE 中的消息队列通过维护已经入队的总数据量的字节计数来获得这些功能。因而,无论何时有新消息块被放入消息队列中,消息队列都将先确定它的长度,然后检查是否能将此消息块放入队列中(也就是,确认如果将此消息块入队,消息队列没有超过它的高水位标)。如果消息队列不能将数据入队,而它又持有一个锁(也就是,使用了 ACE_SYNC ,而不是 ACE_NULL_SYNCH 作为消息队列的模板参数),它就会阻塞调用者,直到有足够的空间可用,或是入队方法的超时( timeout )到期。如果超时 已到期,或是队列持有一个空锁 ,入队方法就会返回 -1 ,指示无法将消息入队。
类似地,当 ACE_Message_Queue 的 dequeue_head 方法被调用时,它检查并确认在出队之后,剩下的数据的数量高于低水位标。如果不是这样,而它又持有一个锁,它就会阻塞;否则就返回 -1 ,指示失败(和入队方法的工作方式一样)。
分别有两个方法可用于设置和获取高低水位标:
//get the high water mark
size_t high_water_mark(void)
//set the high water mark
void high_water_mark(size_t hwm);
//get the low water_mark
size_t low_water_mark(void)
//set the low water_mark
void low_water_mark(size_t lwm)
9.4 使用消息队列迭代器 (Message Queue Iterator)
和其它容器类 的常见情况一样,可将前进( forward )和后退( reverse )迭代器用于 ACE 中的消息队列 。这两个迭代器名为 ACE_Message_Queue_Iterator 和 ACE_Message_Queue_Reverse_Iterator 。它们都需要一个模板参数,用于在遍历消息队列时进行同步 。如果有多个线程使用消息队列,该参数就应设为 ACE_SYNCH ;否则,就可设为 ACE_NULL_SYNCH 。在迭代器对象被创建时,必须将我们想要进行迭代的消息队列的引用 传给它的构造器。
9.5 动态或实时消息队列
如上面所提到的,动态消息队列是其中的消息的优先级随时间变化的队列。实时应用需要这样的行为特性,因而这样的队列在实时应用中天生更为有用。
ACE 目前提供两种动态消息队列:基于最终期限( deadline )的和基于松弛度( laxity )的(参见 [IX ] )动态消息队列。 基于最终期限的消息队列通过每个消息的最终期限来设置它们的优先级。在使用最早 deadline 优先算法来调用 dequeue_head() 方法时,队列中有着最早的最终期限的消息块将最先出队。而基于松弛度的消息队列,同时使用执行时间和最终期限来计算松弛度,并将其用于划分各个消息块的优先级。 松弛度是十分有用的,因为在根据最终期限来调度时,被调度的任务有可能有最早的最终期限,但同时又有相当长的执行时间,以致于即使它被立即调度,也不能够完成。这会消极地影响其它任务,因为它可能阻塞那些可以调度的任务。松弛度把这样的长执行时间考虑在内,并保证任务如果不能完成,就不会被调度。松弛度队列中的调度基于最小松弛度优先算法。
基于松弛度的消息队列和基于最终期限的消息队列都实现为 ACE_Dynamic_Message_Queue 。 ACE 使用策略 ( STRATEGY )模式来为动态队列提供不同的调度特性。 每种消息队列使用不同的“策略”对象来动态地设置消息队列中消息的优先级。每个这样的“策略”对象都封装了一种不同的算法来基于执行时间、最终期限,等等,计算优先级;并且无论何时消息入队或是出队,都会调用这些策略对象来完成前述计算工作。(有关策略模式的更多信息,请参见“设计模式”)。消息策略模式派生自 ACE_Dynamic_Message_Strategy ,目前有两种策略可用: ACE_Laxity_Message_Strategy 和 ACE_Deadline_Message_Strategy 。 因此,要创建基于松弛度的动态消息队列,首先必须创建 ACE_Laxity_Message_Strategy 对象。随后,应该对 ACE_Dynamic_Message_Queue 对象进行实例化,并将新创建的策略对象作为参数之一传给它的构造器。
创建消息队列
为简化这些不同类型的消息队列的创建, ACE 提供了名为 ACE_Message_Queue_Factory 的具体消息队列工厂,它使用工厂方法( FACTORY METHOD ,更多信息参见“设计模式”)模式的一种变种来创建适当类型的消息队列。消息队列工厂有三个静态的工厂方法,可用来创建三种不同类型的消息队列:
static ACE_Message_Queue<ACE_SYNCH_USE> *
create_static_message_queue();
static ACE_Dynamic_Message_Queue<ACE_SYNCH_USE> *
create_deadline_message_queue();
static ACE_Dynamic_Message_Queue<ACE_SYNCH_USE> *
create_laxity_message_queue();
每个方法都返回指向刚创建的消息队列的指针。注意这些方法都是静态的,而 create_static_message_queue() 方法返回的是 ACE_Message_Queue ,其它两个方法则返回 ACE_Dynamic_Message_Queue 。
[ACE系列] ACE学习相关推荐
- ACE高性能网络编程——学习大纲
ACE高性能网络编程 一.课程目标 本次课程针对使用ACE工具包进行高性能网络应用开发,通过本次课程的学习,学员将具备以下能力: 了解ACE的架构和组件: 理解现代操作系统线程模型.并发以及同步机制: ...
- keras系列︱迁移学习:利用InceptionV3进行fine-tuning及预测、完美案例(五)
之前在博客<keras系列︱图像多分类训练与利用bottleneck features进行微调(三)>一直在倒腾VGG16的fine-tuning,然后因为其中的Flatten层一直没有真 ...
- 斯坦福大学CS520知识图谱系列课程学习笔记:第三讲高级的知识图谱是什么样的
这一讲的主题是:一些高级的知识图谱是什么样的.三位讲者分别从他们各自带领的团队构建出来的高质量的知识图谱实例出发,从中我们可以认识到一些大佬们构建的优秀的知识图谱产品有哪些特点和优势,从中对我们自己构 ...
- 斯坦福大学CS520知识图谱系列课程学习笔记:第二讲如何构建知识图谱
上一讲我们学习了知识图谱的一些基本概念: 斯坦福大学CS520知识图谱系列课程学习笔记:第一讲什么是知识图谱 本节课程关于如何构建知识图谱,因为知识图谱的构建是整个知识图谱领域的一个非常核心且基础的工 ...
- 详细介绍Linux shell脚本系列基础学习(列表)
本系列适合Linux初学者,属于Linux入门级教程,主要介绍了Shell的分类.语法格式以及脚本的使用和编写格式等. 不断更新中,是Shell学习的必读经典教程.现主要包含以下文章: Linux S ...
- python3《机器学习实战系列》学习笔记----3.2 决策树实战
前言 一.ID3算法构造决策树 1.1 背景 1.2 信息增益计算 1.3 递归生成决策树 二.使用Matplotlib注解绘制树形图 2.1 Matplotlib注解 2.2 构造注解树 三.测试和 ...
- mysql入门很简单系列视频-学习笔记
mysql入门很简单系列视频-学习笔记 视频链接:mysql入门很简单系列视频 https://www.bilibili.com/video/av14920200/ 以前主要就了解DDL.DML.DC ...
- 区块链知识系列 - 系统学习EVM(二)-存储与安全
区块链知识系列 - 系统学习EVM(一) 特点 EVM出于所谓运算速度和效率方面考虑,采用了非主流的256bit整数. 不支持浮点数 缺乏标准库支持,例如字符串拼接.切割.查找等等都需要开发者自己实现 ...
- 奋斗的小孩系列 FPGA学习altera系列: FPGA学习altera 系列 第二十一篇 数码管设计
奋斗的小孩系列 FPGA学习altera系列: FPGA学习altera 系列 第二十一篇 数码管设计 作者:奋斗的小孩 郝旭帅(转载请注明出处) 大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是 ...
最新文章
- 李开复:发明期已过,AI科学家创业需谨慎(附演讲视频)
- simulink中错误object[id]!=NULL Component: Simulink | Category:Model error
- todo已完成任务_重要主干街路已完成清雪任务
- Hive更换TEZ引擎
- 【C++深度剖析教程32】new/malloc区别 delete/free区别
- Spring Cloud微服务之网关服务创建(十四)完结篇
- 传统emmc所用的sdio接口_SolidGear SD/SDIO/eMMC协议分析仪
- 千里之行始于足下---在“从普通走向优秀”的路上
- Json学习总结(5)——阿里巴巴开源库 Fastjson详解
- 即时通讯学习笔记004---即时通讯服务器种类认知
- iPhone不送充电器?工信部发话了
- 树展示 移动端_企业快速搭建移动BI轻应用,怎么少得了这款BI工具
- R语言基础期末大作业
- 惠普打印机换硒鼓图解_hp硒鼓怎么安装 hp硒鼓安装方法这图文教程】
- 软件项目管理 2.2.项目招投标流程
- 《凤凰项目 一个IT运维的故事传奇》读后感
- LabVIEW编程LabVIEW控制研华PCI 1220运动控制卡例程与相关资料
- 徐培成电商项目-徐培成-专题视频课程
- 大文件传输的三种方式
- cad图纸问号怎么转换文字_CAD中文图纸中文字体变成问号怎么办?不慌这几步教你轻松解决...
热门文章
- CentOS_6.5 x86_64 制作U盘启动方法
- 2020-08-23 html的标签预加载 + css的新知识 + JS的json的标准格式 + 软技能的能说会道vs安静做事
- 网站被攻击怎么处理,快速解决方法
- css如何使下划线位置远,使用css调整下划线和文本之间的距离
- 谈一谈git revert和revert the revert
- Penetration Test 渗透测试
- 西电挑战杯文案编写要求
- 计算机组成原理——关于原码、补码、移码运算及浮点数运算的总结
- UE4蓝图入门知识及UI制作控件事件
- 百度团队前端自动化工程工具fis-parser-node-sass插件安装失败的解决方案