目录
ACE Proactor框架
异步I/O工厂类
ACE_Handler类
前摄式Acceptor-Connector类
ACE_Proactor类
ACE Streams框架
ACE_Model类
ACE_Streams类
ACE Proactor框架
ACE Proactor框架实现了proactor模式,也就是异步网络模式,允许事件驱动的应用高效地多路分离和分配由异步I/O完成所触发的服务请求。
ACE Reactor 框架往往会与反应式I/O模型,起使用。基下这一模模型的应用向反应器登记事件处理器对象,反应器会在可以执行一个或多个所需的I/O操作(比如在 socke 上:接收数据,这很可能能够立刻完成)时通知这些事件处理器对象。I/O操作常常在单线程中执行,由反应器的事件分派循环驱动。
缓和反应式I/O的瓶颂效应的一种途径是结合多线程模型来使用同步I/0。多线程有助于使应用的 I/O 操作并行化,并能改善性能。但是,给设计增加多个线程需要使用适当的同步机制来避免发生并发事故。
要缓和反应式 I/O的瓶须效应,而又不引入同步I/O 和多线程的复杂性和开销,前摄式(proactor) I/O模型常常是一种可伸缩性更好的途径。该模型允许应用通过以下两个阶段来执行I/O操作:
1、应用可以在多个 I/O句柄上并行地发起一个或多个异步I/O操作,而无需等待它们完成。
2、在每个操作完成时,OS会通知应用定义的完成处理器,由它随后对已完成的I/O操作的结果进行处理。
前摄式 I/O 模型的两个阶段在本质上是反应式模型中的两个阶段的反转,应用在反应式模型中:
1、使用事件多路分离器来确定某个I/O操作何时可以进行,并且有可能刻完成。然后
2、同步地执行操作。
ACE Proactor 框架简化了使用前摄式 I/O模型的程序的开发。在这一语境中,ACE Proactor 框架
负责:
ACE Proactor 框架的主要类如下表所示:
上述这些类按照Proactor模式扮演了以下角色:
异步I/O基础设施层类 执行不依赖于应用的策略:发起异步I/O操作,将完成事件多路分离给它们的完成处理器,并随即分派与之相关联的完成处理器挂钩方法。ACE Proactor框架中的基设施层类包括ACE_Asynch_Acceptor、ACE_Asynch_Connector、ACE_Asynch_Result、ACE_Asynch_Read_Stream、ACE_Asynch_WriteStream,以及ACE Proactor 的各种实现。其础设施层述使用了 ACE_Time_Value 和 ACE 定时器队列类。
应用层类 包含的是完成处理器,它们在其挂钩方法中执行心用定义的处理。在ACEProactor框架中,这些类是ACE_Handler和/或ACE_Service_Handler 的后代。
ACE Proactor 框架的威力来源于它的基础设施类和应用类之间的事务分离。通过解除“完成多路分离和分派机制”与“应用定义的事件处理策略”的耦合,ACE Proactor提供了以下好处:
改善可移植性 应用可以在许多平台上利用前摄式IO模型,这些平台有着不同的异步 I/O机制:在 Windows上使用overlapped I/O,在实现了POSIX.4 RcaltimeExtension 标准的 Asynchronous IO(AI0)选项的平台(包括 HP-UX、IRIX、Linux、LynxOS和 Solaris)使用 AIO.
使完成检测、多路分离和分派自动化 ACE Proactor架将本地OS的 I/O发起和完成多路分离API、以及定时器支持隔离在基础设施层框架类中。应用可以使用这些面向对象机制来发起异步操作,并且只需要实现应用定义的完成处理器。
支持透明的可扩展性 ACE Proactor框架使用了Bridge模式来输出具自统一的、定义良好的行为的接口。这一设计允许框架内部进行变化,并适应OS 所提供的各种异步I/O实现及其缺陷,而不要求在应用层进行任何动。
增加复用,并使易错的编程细节降至最少 通过分离“异步IO机制”与“应用定义的策略及行为”,可以跨越许多不同的应用领域对 ACE Proactor 框架进行复用。
线程安全性 应用可以利用OS台所提供的I/O并行性,尤需使用复杂的应用级同步策略,在单个线程中处理“应用操作发起和完成处理代码”时,要遵循的数据访问原则很简单,比如“在IO 完成之前,不要对已经发给OS的I/O 缓冲区进行操作”。
异步I/O工厂类
与反应式和同步 I/O模型相比,前摄式I/O模型通常史难以编程,因为:
I/O发起和完成是必须分别处理的不同活动。
可以同时发起多个I/O操作,这需要进行更多的记录。
当多个I/O操作同时完成时,不存在确定的完成次。
在多线程化服务中,完成处理器可能会在与发起I/O操作的线程不同的线程中执行。
因此,前摄式I/O模型需要个上!来发起异步I/O操作。因为多个I/O操作可以同时执行,并以任意的次序完成,前摄式模型还需要在每个异步操作和它的参数(比如IO句柄、数据缓冲区以及缓冲区大小)以及将对操作结果进行处理的完成处理器之间进行显式的绑定。
在理论上,设计一些类来生成并步I/O操作并将它们绑定到它们的完成处理器,相对来说应该是直截了当的。但是实际上,在今天流行的各种 OS平台上异步 I/O 足通过不同的途径实现的,设计内此而变得更为复杂化。下面是两个常见的示例:
Windows Windows ReadFile()和 WriteFile()系统函数既可以执行同步 I/O,也叫以发起overtapped I/O操作。
POSIX POSIX aio_read()和 aio_write()函数分别用于发起异步读写操作。这两个函数与在 ACE的 IPC wrapper facade 类中使用的 read()和 wrie()(以及 Sockets recv()和 send())函数。
上面所讨论的所有异步I/O机制都使用了I/O句柄来表示用于进行I/O的IPC信道或文件, ACE Proactor 框架定义了一组类,可在各种IPC机制上发起异步I/O。这些类增强可移植性,使复杂性降至最低,。本书检查了了两种最受欢迎的用于网络化应用的类:ACE_AsynchRead_Stream和ACE_Asynch_Write_Stream。
CE_AsynchRead_Stream和ACE_Asynch_Write_Stream是两个工厂类,它们使得应用能够发起可移植的异步read()和 write()操作。这以个类提供了以下能力:
它们可在面向流的IPC机制(比如TCP socket)上发起异步操作。
它们将 I/O句柄、ACE_Handler 对象,以及 ACE_Proactor绑定在一起,以正确而高效地处理 I/O完成事件
它们创建这样的对象,它将操作的各个参数从ACE Proactor框架携带到它的完成处理器。
它们派生自 ACE_Asynch_Operation,后者提供的接口可用于“初始化对象和请求取消术完成的I/O 操作”。
ACE_Asynch_Read_Stream和ACE_Asynch_Write_Stream定义了嵌套的Result 类来表示操作和其参数之间的绑定。ACE Proaclor框架将常用的面问结果(Results-oriened)的行为抽象进ACE_Asynch_Result 类中,嵌套的 Result 类就是从 ACE_Asynch_Resut 类派的。合在一起,这组类提供了具有以下能力的完成处理器:
它可以获取某个I/O操作原来的参数,比如请求传输的学节计数和内存地址。
它可以确定与其相关联的操作的成功或失败。
可以将一个异步完成令牌(ACT,Asynchronous Compleron Token)传给它。ACT 提供了一种方法来扩展在操作发起器和完成处理器之间所流的信息的数量和类型。
下面的图表展示了ACE_Asynch_Read_Stream、ACE_Asynch_Write_Stream、ACE_Asynch_Result和它们的Result嵌套类接口。
ACE_Asynch_Read_Stream的关键方法如下所示:
ACE_Asynch_Write_Stream的关键方法如下所示:
open()方法将异步I/O 工厂对象绑定到:
用于发起I/O操作的句柄。
用于检测和多路分离I/O操作的完成事件的ACE_Proactor 对象。
对前摄器所分派的I/O完成进行处理的ACE Handler 对象。
open函数中act 参数是一个异步完成令牌,它将与通过 I/O工厂对象发出的每个 I/O 操作关联在 起。当操作完成时,可以使用 ACE_Asynch_Resut 中的 act()方法来获取 act 参数。但是,这个特性是 Windows特有的。
read()和 wnite()为法使用了ACE_Message_Block 分别进行接收和发送,提供了以下好处:
简化了缓冲区管理 因为异步I/O发起和完成是在不同的类中处理的,缓冲区地址、可用空问以及已用空间所涉及的信息必须与每个I/O操作关联在一起。复用ACE_Message_Block 高效而可移慎地满足了这些需要。
使传输计数更新自动化 write()方法从消息块的读指针所指向的地方开始传输字节,也就是,它从消息中读出字节。相反,read()方法从消息块的写指针所指向的地方开始将数据读入消息块,也就是,它将新的字节写到消息中。在成功地完成I/O操作之后,消息块的读和写指针都已被更新,正确反映了成功传输的字节的数目。因此,应用无需调整消息缓冲区指针或大小,因为 ACE Proactor框架会对此进行自动处理。
易于与其他ACE框架集成 ACE_Message_Block提供了一种方便的机制来获取或转发数据,以在 ACE Task 框架、ACE Acceptor-Connector 框架和 ACE Streams框架中进一步处理这些数据。
与ACE IPC wrappcr facade相反,ACE_Asynch_Read_Stream和 ACE_Asynch_Write_Stream 没有封装任何底层IPC 机制,而是定义了发起异步 I/O 操作的接口。这一设计带来了以下好处:
1、它允许我们在 ACE Proactor 框架中复用 ACE 的 IPC wrapper facade 类,比如 ACE_SOCK_Stream和 ACE_SPPE_Stream,避免重义创建一组平行的、从能用于Proactor 框架中的 IPC类。
2、通过只开放必需的I/O操作发起器,强制性地建了个结构来避免I/O 句柄的误用。
3、通过将I/O句柄给予异步操作工厂,它方便了开发者将相同的IPC类用于同步和异步I/O。
因此,使用 ACE编写的网络化应用可以使用同步、反应式和前摄式/O的任意组合。可以在编译时选择使用哪一种I/O机制,如果需要的话,也可以在运行时才作出选择。
使用ACE Proactor框架,重新实现上一篇文章实现的客户日志daemon服务。尽管在前摄式客户日志daemon 服务中使用的类与在 Acceptor-Connector 版术中使用的类是类似的,前摄式版本使用了单个应用线程来发起它的所有的I/O操作,并处理其完成。下面展示了 ACE Proactor框架设计的主要类:
下图描述了这些类之间的的交互关系:
这个示例代码位于AIO_Client_Logging_Daemon.cpp文件中。AIO_Output_Handler类将日志记录转发给服务器日志daemon,其部分实现如下所示:
//继承自ACE_Task 以复用ACE_Message_Queue
//同样继承自ACE_Service_Handler 处理完成事件 充当AIO_CLD_Connector异步连接工厂的目标
class AIO_Output_Handler
: public ACE_Task<ACE_NULL_SYNCH>, //trait为ACE_NULL_SYNCH 所有的单体访问发生在一个线程中
public ACE_Service_Handler {
public:
AIO_Output_Handler () : can_write_ (0) {}
virtual ~AIO_Output_Handler ();
// Entry point into the <AIO_Output_Handler>.
virtual int put (ACE_Message_Block *, ACE_Time_Value * = 0);
// Hook method called when server connection is established.
// 服务连接时进行回调
virtual void open (ACE_HANDLE new_handle,
ACE_Message_Block &message_block);
// 抑制编译警告/错误所需的琐碎实现
virtual int open (void *args)
{
return ACE_Task<ACE_NULL_SYNCH>::open (args);
}
protected:
ACE_Asynch_Read_Stream reader_; // Detects connection loss 用于检测服务器连接何时关闭
ACE_Asynch_Write_Stream writer_; // Sends to server 用于发起异步的write()操作 发送日志给服务器日志daemon
int can_write_; // Safe to begin send a log record?
// Initiate the send of a log record
void start_write (ACE_Message_Block *mblk = 0);
// Handle disconnects from the logging server.
virtual void handle_read_stream
(const ACE_Asynch_Read_Stream::Result &result);
// Handle completed write to logging server.
virtual void handle_write_stream
(const ACE_Asynch_Write_Stream::Result &result);
};
当服务器日志daemon连接被建立时,ACE Proactor框架会被分配下面的open()挂钩方法下:
void AIO_Output_Handler::open
(ACE_HANDLE new_handle, ACE_Message_Block &) {
//将socket的发送缓存区设置到最大尺寸
ACE_SOCK_Stream peer (new_handle);
int bufsiz = ACE_DEFAULT_MAX_SOCKET_BUFSIZ;
peer.set_option (SOL_SOCKET, SO_SNDBUF,
&bufsiz, sizeof bufsiz);
//初始化reader_、writer_对象,它们都将this对象指定为完成处理器,并用于发出操作新socket句柄
//和同一个ACE_Proactor用于打开连接
//第三个参数为0 完成键设施没有被使用
//在初始化之后,reader_、writer_对象用于完成异步操作
reader_.open (*this, new_handle, 0, proactor ());
writer_.open (*this, new_handle, 0, proactor ());
//在反应器中为输入事件登记了服务器socket
//在前摄式模型中,为一个字节发送异步read操作
ACE_Message_Block *mb = 0;
ACE_NEW (mb, ACE_Message_Block (1));
reader_.read (*mb, 1);
//忽略SIGPIPE信号 如果连接被关闭,异步的write()也不会中止程序了
ACE_Sig_Action no_sigpipe ((ACE_SignalHandler) SIG_IGN);
no_sigpipe.register_action (SIGPIPE, 0);
//为了编码部分写,每次只传输一个日志记录
//can_write_标志指示开始写新的日志记录是否安全
can_write_ = 1;
start_write (0);
}
因为这个连接是新打开的,现在写记录是安全的,所以我们设置标志,并随即调用下面的start_write()万法来发起异步的 write()操作:
void AIO_Output_Handler::start_write (ACE_Message_Block *mblk) {
//通过传入的参数决定调用哪个方法
//如果为NULL 取出队列中的第一个消息并发送
if (mblk == 0) {
ACE_Time_Value nonblock (0);
getq (mblk, &nonblock);
}
if (mblk != 0) {//不为空 表明立刻开始写
can_write_ = 0;//置为0 防止当前的发送结束前 有其他消息块被发送
//writer_对象发起一个异步的write()来操作消息块
if (writer_.write (*mblk, mblk->length ()) == -1)
ungetq (mblk);
}
}
当从日志客户接收到日志记录时,它们会通过下面的put()方法(新实现自ACE_Task)被传给AIO_Output_Handler:
int AIO_Output_Handler::put (ACE_Message_Block *mb,
ACE_Time_Value *timeout) {
if (can_write_) { start_write (mb); return 0; }
return putq (mb, timeout);
}
如果没有 write()操作在进行的话,调用 start_write()来立刻发起对指定的 ACE_Message_Block 的发送。如果现在不能开始 write(),我们将消息放入队列中,以备后用。
ACE_Handler类
在前摄式和反应式I/O模型之间的一个主要区别是前摄式I/O发起和完成是分別发生的两个不同步骤。而且,这两个步骤可以发生在不同的控制线程中。为发起和完成处理使用不同的类避免了将两者无谓地耦合在一起。上小节描述了用于发起异步I/O操作的 ACE_Asynch_Read_Stream 和ACE_Asynch_Write_Stream 类,本节则将聚焦于 I/O 完成处理。
完成事件表示先前发起的 I/O 操作已经结束。为了正确而高效地处理I/O 操作的结果,除了结果而外,完成处理器还必须知道为此操作指定的所有参数。这些信息总共包括:
所发起的是何种类型的操作
操作是否成功完成。
错误代码,如果操作失败的话。
标识通信端点的I/O句柄。
用于传输的内存地址。
所请求的和实际传输的字节数目
异步 I/O 完成处理所需要的信息比在 ACE Reacror 框架中的回调方法中可用到的信息要更多。因此,上述文章介绍过的 ACE_Event_Handler 类不适于用在 ACE Proactor框架中。因为完成处理也依赖于底层 OS 平台所提供的异步I/O机制,在可移植方面也存在着问题。这也是ACE Proactor框架为何要提供 ACE_Handler 类的原因。
ACE_Handler 是 ACE Proactor 框架中的所有异步完成处理器的基类。这个类提供了以下能力:
其关键方法如下:
handle_time_out()方法在通过 ACE_Proactor 调度的定时器到期时被调用。它的tv 参数是定时器被调度时指定的绝对到期时间。取决于活动水平和分派处理器的延迟,实际时问可能会有所不同。注意这一行为与传给 ACE_Event_Handler::handle timeout()的 ACE Time_Value 有细微低不同,后者是定时器挂钩方法被ACE Reactor 框架实际分派时的时间。
在调用 hande_read_stream()和 handle_write_stream()拌钩方法时所传入的是指向“与已经定成的并步操作相关联的Resut对象”的引用。图8.2显示了可从handle_read stream()和handle_write_stream()中访问的最为有用的Result对象方法及其相关语境;下表也列出了这些方法:
//继承自ACE_Asynch_Result ACE_Asynch_Result中有success等方法
class ACE_Export Result : public ACE_Asynch_Result
{
/// The concrete implementation result classes only construct this
/// class.
friend class ACE_POSIX_Asynch_Read_Stream_Result;
friend class ACE_WIN32_Asynch_Read_Stream_Result;
public:
/// The number of bytes which were requested at the start of the
/// asynchronous read.
size_t bytes_to_read () const;
/// Message block which contains the read data.
ACE_Message_Block &message_block () const;
/// I/O handle used for reading.
ACE_HANDLE handle () const;
/// Get the implementation class.
ACE_Asynch_Read_Stream_Result_Impl *implementation () const;
protected:
/// Constructor.
Result (ACE_Asynch_Read_Stream_Result_Impl *implementation);
/// Destructor.
virtual ~Result ();
/// The implementation class.
ACE_Asynch_Read_Stream_Result_Impl *implementation_;
};
private:
void operator= (const ACE_Asynch_Read_Stream &) = delete;
ACE_Asynch_Read_Stream (const ACE_Asynch_Read_Stream &) = delete;
};
AIO_Input_Handler继承自ACE_Service_Handler,ACE_Service_Handler继承自ACE_Handler,AIO_Input_Handler的定义如下:
class AIO_Input_Handler : public ACE_Service_Handler {
public:
AIO_Input_Handler (AIO_CLD_Acceptor *acc = 0)
: acceptor_ (acc), mblk_ (0) {}
virtual ~AIO_Input_Handler ();
// Called by ACE_Asynch_Acceptor when a client connects.
virtual void open (ACE_HANDLE new_handle,
ACE_Message_Block &message_block);
protected:
enum { LOG_HEADER_SIZE = 8 }; // Length of CDR header
AIO_CLD_Acceptor *acceptor_; // Our creator
ACE_Message_Block *mblk_; // Block to receive log record
ACE_Asynch_Read_Stream reader_; // Async read factory
// Handle input from logging clients.
virtual void handle_read_stream
(const ACE_Asynch_Read_Stream::Result &result);
};
日志客户以CDR格式发送每个日志记录;这种格式的记录是以一个定长的头开始的。这个头有一个ACE_CDR::Boolean,用来指示字节序;以及一个 ACE_CDR::Ulong,存放跟在头后面的有效内容(Payload)的长度。我们从CDR编码和对齐规则可知,头的长度是8字节,所以我们将LOG_HEADER_SIZE枚举符定义为 8。
当日志客户连接到客户日志daemon 时,ACE Proactor 框架会分派下而的open()挂钩方法:
//当有客户连接到客户日志daemon时调用
void AIO_Input_Handler::open
(ACE_HANDLE new_handle, ACE_Message_Block &) {
reader_.open (*this, new_handle, 0, proactor ());
//分配一个ACE_Message_Block 对象来接收客户的日志记录头
ACE_NEW_NORETURN
(mblk_, ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE));
// Align the Message Block for a CDR stream
ACE_CDR::mb_align (mblk_); //根据ACE_CDR 编码进行调整
reader_.read (*mblk_, LOG_HEADER_SIZE);//执行read操作 执行完会执行下面的handle_read_stream
}
这个方法分配一个 ACE_Message_Block接收来自客户的日志记录头。但它所分配的空间小仅足以容纳头,在许多情况下,也足以容纳整个的记录。此外,还可以按调整其大小。块的写指针针对CDR 解整编作了适当的调整,并随即被传给一个用以接收头的片步read()操作,
在read()操作完成时,ACE Proactor框架分派下面的完成处理器方法:
//result为返回的执行结果
void AIO_Input_Handler::handle_read_stream
(const ACE_Asynch_Read_Stream::Result &result) {
//如果read失败 则关闭连接 删除this 释放资源
if (!result.success () || result.bytes_transferred () == 0)
delete this;
//如果接收的字符比请求的要少 则再开启一个异步read接收剩下的字节
//再请求将更多数据增加到消息块时,无需调整任何指针
else if (result.bytes_transferred () < result.bytes_to_read ())
reader_.read (*mblk_, result.bytes_to_read () -
result.bytes_transferred ());
//已经接收了被请求的所有数据 并且其大小是日志记录头的大小
else if (mblk_->length () == LOG_HEADER_SIZE) {
//接收头部信息 使用ACE_InputCDR 解除头部整编 获取记录的有效字节
ACE_InputCDR cdr (mblk_);
ACE_CDR::Boolean byte_order;
cdr >> ACE_InputCDR::to_boolean (byte_order);
cdr.reset_byte_order (byte_order);
ACE_CDR::ULong length;
cdr >> length;
//调整mblk_大小,并且发起一个异步read来接收有效内容
//这个方法会再完成时被调用 如果有必要,会继续发起异步read 直到接受了完整的记录或发生错误
mblk_->size (length + LOG_HEADER_SIZE);
reader_.read (*mblk_, length);
}
//接收完了数据 通过put 对记录进行转发
else {
if (OUTPUT_HANDLER::instance ()->put (mblk_) == -1)
mblk_->release (); //转发后进行释放
//为下一个日志记录分配一个新的ACE_Message_Block 并发起一个异步read读取来接收它的头
ACE_NEW_NORETURN
(mblk_, ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE));
ACE_CDR::mb_align (mblk_);
reader_.read (*mblk_, LOG_HEADER_SIZE);
}
}
AIO_Output_Handler::start_write()方法发起异步write()操作来发送日志记录给服务器daemon。当write()操作完成时,ACE Proactor框架分派下面的方法:
void AIO_Output_Handler::handle_write_stream
(const ACE_Asynch_Write_Stream::Result &result) {
ACE_Message_Block &mblk = result.message_block ();
//如果write操作失败 将消息块的rd_ptr复位至块的开头
//假定write失败 socket会关闭 并在后面被重新连接 到时候消息块将重头发送
if (!result.success ()) {
mblk.rd_ptr (mblk.base ());
ungetq (&mblk);
}
else {
//socket没有关闭得到情况下设置can_write_
can_write_ = handle () == result.handle ();
//如果整个块都已被写 就释放它 如果可以进行另外的write 就发起它
if (mblk.length () == 0) {
mblk.release ();
if (can_write_) start_write ();
}
//write成功执行 socket还在连接 但只发送了消息的一部分 继续异步write发送
else if (can_write_) start_write (&mblk);
//消息部分发送 但socket已经断开
//将消息块的rd_ptr复位至块的开头 以便重新发送
else { mblk.rd_ptr (mblk.base ()); ungetq (&mblk); }
}
}
转发日志序列如下图所示:
如果 write()操作失败的话,我们不会尝试清理 socket,因为它的未完成的 read()操作会检测这一问题。在服务器日志 daemon 关闭socket 时,在 AIO_Output_Handler::open()中开始的分步read()操作会完成。在完成时,ACEProacror框架调用下面的方法:
void AIO_Output_Handler::handle_read_stream
(const ACE_Asynch_Read_Stream::Result &result) {
//释放为read操作分配的所有ACE_message_block
result.message_block ().release ();
writer_.cancel (); //取消任何未完成的write操作
ACE_OS::closesocket (result.handle ()); //关闭socket
//如果现在的连接已关闭 句柄设置为ACE_INVALID_HANDLE
handle (ACE_INVALID_HANDLE);
//在重新建立连接之前阻止任何日志记录的发起
can_write_ = 0; //复位can_write_ 标识
CLD_CONNECTOR::instance ()->reconnect ();
}
接下来讲介绍怎样在ACE Proactor框架中被动和主动地建立连接。
前摄式Acceptor-Connector类
ACE_Asynch_Acceptor是Acceptor-Connector模式中的接收器角色的另一种实现 ,提供以下能力:
ACE_Asynch_Connector是Acceptor-Connector模式中的连接器角色,提供以下能力:
与上述文章描述的 ACE Acceptor-Connector架不同,这两个类只建了TCP/IP 连接。ACE Proactor框架聚焦于封装操作,而不是I/O 句柄,而这两个类封装的是 TCP/IP连接建立操作。无连接的IPC机制(例如,UDP和文件I/O)不需要进行连接设置,所以它们可以直接与ACE Proactor框架的I/O工厂类一起使用。
与ACE_Acceptor和ACE_Connector类似,ACE_Asynch_Acceptor和ACE_Asynch_Connector 是模板类工厂,它们可以创建服务处理器来在新连接上执行服务。ACE_Asynch_Acceptor和ACE_Asynch_Connector 的模板参数都是工厂所生成的服务类,称为ACE_Service_Handler。这个类充当了来自 ACE_Asynch_Acceptor和 ACE_Asynch_Connector 的连接完成的目标。
ACE_Scrvice Handler 提供了以下能力:
下表描述了ACE_Asynch_Acceptor的关键方法:
open()方法初始化进行侦听的 TCP socket,并发起一个或多个异步 accept()操作。如果给reissue_accept的实参是1(缺省值),新的accept()操作将会在需要时自动启动。
ACE_Asynch_Acceptor 实现了ACE_Handler::handle_accept()方法来处理每个accept()的完成,如下所示:
1、收集代表每个新连接的端点的 ACE_INET_Addr。
2、如果传给 open()的 validate_new_connection 参数为1,调用 validale_connection()方法,将相,连对端的地址作为参数传给它。如果validare_connection()返列-1,连接被中止。
3、调用 make_handler()挂钩方法来为每个新连接获取服务处理器。缺省实现使用了operator new来动态分配新的处理器。
4、设置新处理器的 ACE Proactor 指针。
5、如果传给open(的pass_address 参数为1,用本地和对端地址做参数,调用ACE Service Handler::addresses()方法。
6、设置新连接的 IO句柄,并调用新的服务处理器的open()方法。
ACE_Asynch_Connector 类提供的方法与ACE_Asynch_Acceptor 中的那些方法类似。如下表所示:
open()方法所接受的参数要少于 ACE_Asynch_Acceptor:open()、特别地,因为每次 connect()操作的寻址信息可能会不同,所以要在传给connect()方法的参数中指定它。
ACE_Asynch_Connector实现了ACE_Handler:handle_connect()来处理每次连接完成,处理步骤与上面的ACE_Asynch_Acceptor 的处理步骤机同。
ACE Proactor 框架中的每个网络化应用服务类都派生门ACE_Service_Handler。下表显示其关键方法:
ACE_Asynch_Acceptor 和ACE_Asynch_Connector都会为所建立的每个连接调用 ACE_Service_Handler:open()挂钩方法。handle 参数是已连接的 socket 的句柄。如果传给ACE_Asynch_Acceptor:open()的 bytes_to_read 参数大于0的话,ACE_Message_Block 参数可能会含来自对端的信息。因为这种 Windows特有的设施常常会与非IP协议一同使用,在这出我们没有讨论其使用。ACE Proactor框架会管理这个ACE_Message_Block,所以服务无需关心它。
如果服务处理器需要新连接的本地或对端地址,它必须实现 addresses()挂钩方法来连接被建立时捕捉它们。如果传给异步连接工厂的pass_address参数为1,ACE Proactor框架就会调用 adresses()方法。这个方法在 Windows上更有意义,因为如果使用的是异步连接建立,我们无法通过其他任何途径来获取连接地址。
前摄式实现中的类被划分为输入角色和输出角色,下面解释这两种角色:
输入角色。前摄式客户日志 daemon 的输入角色是由 AIO_CLD_Acceptor 和 AIO_Input_Handler类来完成的。上面已经介绍了AIO_Input_Handler,所以这里我们将聚焦于 AIO_CLD_Acceptor,AIO_CLD_Acceptor派生自ACE_Asynch_Acceptor。AIO_CLD_Acceptor的类定义如下所示:
class AIO_CLD_Acceptor
: public ACE_Asynch_Acceptor<AIO_Input_Handler> {
public:
//FUZZ: disable check_for_lack_ACE_OS
// Cancel accept and close all clients.
//取消接受并关闭所有客户端。
void close ();
//FUZZ: enable check_for_lack_ACE_OS
// Remove handler from client set.
// 从客户端集中删除处理程序。
void remove (AIO_Input_Handler *ih)
{ clients_.remove (ih); }
protected:
// 服务处理程序工厂方法
// Service handler factory method.
virtual AIO_Input_Handler *make_handler ();
// Set of all connected clients
// 所有连接的客户端的集合
ACE_Unbounded_Set<AIO_Input_Handler *> clients_;
};
因为 ACE Proactor 框架只追踪活动的 IO 操作,它没有像 ACE Rcactor 框架加样维护一个已登记的处理器集。因此,应用必须在必要的时候定位并清理处理器。在本章的示例中,AIO_Input_Handler对象是被动态分配的,所以在服务关闭时必须能够易于访问它们(从而进行清理)。为了满足这一需求,AIO_CLD_Acceptor:clients_成员是一个ACE_Unbounded_Set,用于存放指向所有活动的AIO_Input_Handler 对象的指针。当日志客户连接到这个服务器时,ACE_Asynch_Acceptor::handle_accept()调用下面的工厂方法:
AIO_Input_Handler * AIO_CLD_Acceptor::make_handler () {
AIO_Input_Handler *ih;
ACE_NEW_RETURN (ih, AIO_Input_Handler (this), 0);
if (clients_.insert (ih) == -1)
{ delete ih; return 0; }
return ih;
}
AIO_CLD_Acceptor 重新实现了 make_handler()工厂方法,该方法在 ciients_中追踪每个已分配的服务处理器的指针。如果因为某种原因,无法插入新处理器的指针,就将它删除;返0将强制ACE Proaclor框架关闭新接受的连接。
make_handler()挂钩方法将其对象指针传给每个动态分配的 AlO_input_Handler。当AIO_Input_Handler检测到某个失败的 read()时(很可能是因为日志客户关闭了连接),其handle_read_steam()方法就会将自己删除。AIO_Input_Handler 析构器清理所持有的全部资源,并调用 AIO_CLD_Acceptor:remove()方法米从clients_集内移除自己,如下所示:
AIO_Input_Handler::~AIO_Input_Handler () {
reader_.cancel ();
ACE_OS::closesocket (handle ());
if (mblk_ != 0) mblk_->release ();
mblk_ = 0;
acceptor_->remove (this);
}
当这个服务在 AIO_Client_Logging_Daemon::svc()方法中关闭时(下面会介绍),我们将通过调用下面的 close()方法来清理余下的所有AIO_Input_Handler 连接和对象:
void AIO_CLD_Acceptor::close () {
ACE_Unbounded_Set_Iterator<AIO_Input_Handler *>
iter (clients_.begin ());
AIO_Input_Handler **ih;
for (; iter.next (ih); ++iter)
delete *ih;
}
输出角色。 前摄式客户日志daemon的输出角色是出AIO_CLD_Connector和AIO_Output_Handler类来完成的。客户日志daemon将AIO_CLD_Connector用于:
1、建立(并在需要时重新建立)与服务器日志daemon的TCP连接
2、实现“与服务器日志 daemon 所进行的 SSL认证”的连接器端(服务器日志 daemon 的 SSL认证)。
它随后使用 AIO_Output_Handler来将日志记录从相连日志客户异步地转发到服务器日志daemon 中,下面是 AIO_CLD_Connector 类的定义:
class AIO_CLD_Connector
: public ACE_Asynch_Connector<AIO_Output_Handler> {
public:
enum { INITIAL_RETRY_DELAY = 3, MAX_RETRY_DELAY = 60 };
// Constructor. 构造函数
AIO_CLD_Connector ()
: retry_delay_ (INITIAL_RETRY_DELAY), ssl_ctx_ (0), ssl_ (0)
{ open (); }
// Destructor frees the SSL resources. 析构函数 释放SSL资源
virtual ~AIO_CLD_Connector () {
SSL_free (ssl_);
SSL_CTX_free (ssl_ctx_);
proactor ()->cancel_timer (*this);
}
// Hook method to detect failure and validate peer before
// opening handler.
// 用于在打开处理程序之前检测失败并验证
virtual int validate_connection
(const ACE_Asynch_Connect::Result& result,
const ACE_INET_Addr &remote, const ACE_INET_Addr& local);
// Re-establish a connection to the logging server. 重连函数
int reconnect () { return connect (remote_addr_); }
protected:
// Hook method called on timer expiration - retry connect 重连超时函数
virtual void handle_time_out
(const ACE_Time_Value&, const void *);
// Template method to create a new handler 创建handler工厂方法
virtual AIO_Output_Handler *make_handler ()
{ return OUTPUT_HANDLER::instance (); }
// Address at which logging server listens for connections.
// 存储服务器连接地址信息
ACE_INET_Addr remote_addr_;
// Seconds to wait before trying the next connect
// 尝试下一次连接前等待的秒数
int retry_delay_;
// The SSL "context" data structure.
SSL_CTX *ssl_ctx_;
// The SSL data structure corresponding to authenticated SSL
// connections.
SSL *ssl_;
};
typedef ACE_Unmanaged_Singleton<AIO_CLD_Connector, ACE_Null_Mutex>
CLD_CONNECTOR;
AIO_CLD_Connector 类是通过 CLD_CONNECTOR typedef,作为不受管理的单体而被访问的。在 AIO_CLD_Connector 被实例化时,它的构造器会调用ACE_Asynch_Connector:open()方法。在缺省情况下,validate_connection()方法将会在每个connect()连接尝试完成时被调用。
ACE_Proactor类
ACE_Proactor 实现了Facade 模式来定义这样一个接口:应用可将其用于可移植而灵活地访问 ACE Proactor 框架的各种特性。这个类提供了以下能力:
下图显示了ACE Proactor的接口,这个类有着一个丰富的接口,输出了ACE Proactor框架中的所有特性。因此,我们将其方法描述分组进下面描述的4个范畴中。
1、生命周期管理方法 下面的方法初始化、销毁和访问ACE_Proactor实例:
可以通过两种方式来使用 ACE_Proactor:
1、通过上表中所示的instance(),作为单体来使用。
2、通过实例化一个或多个实例来使用。这一能力可被用于在一个进程中文持多个前摄器。每个前
摄器常常会与运行在特定优先级上的一个线程关联在一起。
2、事件循环管理方法 ACE Proactor框架支持控制的反转。与ACE Rcactor框架类似,ACE Proactor实现了以下事件循环方法,用以控制应用的完成处理器分派:
ACE Proactor 事件循环与ACE Reactor 框架所提供的事件循环是相互分离的。为了在同一应用中既使用ACE Reactor事件循环,又使用ACE Proactor事件循环,并且跨越所有异步I/O平台保持可移植性,必须在不同的线程中执行这两种事件循环。但是,ACE_Proactor的Windows 实现可以通过向ACE_WFMO_Reactor 实例登记其I/O 完成端口句柄来将两种事件循环机制连结在一起,从而允许它们被同时用于一个线程中。
3、定时器管理 在缺省情况下,ACE Proactor 使用了ACE_Timer_Heap 定时器队列机制来根据事件处理器的“超时最后期限”调度和分派它们。ACE_Proactor所开放的定时器管理方法包括:
当定时器到期时,ACE_Handler::handle_time_out()会在所登记的处理器上被分派
4、I/O操作 facilitator方法 平台的并少I/O实现细节对于 ACE_Proactor 类来说是可见的,这有益于操作发起和完成事件处理。使ACE Proactor成为平台特有知识的中央协调者可防止在ACE Proactor 框架中的各个类之间产生耦合。特别地,ACE Proactor 框架使用了 Bridge 模式。
ACE_Asynch_Read_Stream 和 ACE_Asynch_Wrile_Stream 使用了Bridge 模式来访问它们的 I/O 操作工厂的灵活实现,这些实现适用于特定的OS平台。因为ACE_Proactor 是这些平台特有知识的协调者,它定义了以下方法:ACE_Asynch_Read_Stream和ACE_Asynch_Write_Stream 类使用了这些方法。
ACE_Proactor 类引用了类型 ACE_Proactor_lmpl 的一个对象。依赖于平台特有机制的所有工作都会被转给Proactor 实现类来处理。下面我们简要地描述一下针对特定平台的ACEProactor 框架实现。
ACE_WIN32 Proactor类
ACE_WIN32_Proactor 是 Wndows 上的 ACE_Proactor 实现。这个类可以在 Windows NT 4.0及更新的 Windows 平台上工作,比如 Windows2000 利 WindowsXP。但是,它不能在 Windows 95、98、ME或是CE上工作,因为这些平台不支持异步I/O。
实现综述。ACE_WIN32_Proactor 使用了I/O 完成端口进行完成事件检测。在初始化异步操作工(比如 ACE_Asynch_Read_Stream 或 ACE_Asynch_Write_Stream)时,I/O句柄与前摄器的 I/O完成端口被关联在一起。在这种实现中,Windows GetQueuedCompletionStatus()函数负责执行事件循环。
所有为与ACE WIN32_Proactor一起使用而定义的Result类都派生自 Windows OVERLAPPED结构。取决于正在执行的操作,每个类还增加了额外的信息。当GeQucuedCompletionStatus()返回指向已完成操作的OVERLAPPED结构的指针时,ACE_WIN32_Proactor 将其转换为指向Resut对象的指针。这一设计使得定成事件能够在 I/O操作完成时,被高效地分派给正确的派生自ACE_Handler的完成处理器。
并发考虑事项。 多个线程可以同时执行一个ACE_WIN32_Proactor 的事件循环。内为所有的事件登记和分派都是由IO完成端口机制,而不是ACE_WIN32_Proactor来处理的,我们无需像在ACE_WFMO_Reactor实现中那样,同步对“与登记有关的数据结构”的访问。
定时器队列到期管理在一个单独的线程中处理,这个线程由ACE_WIN32_Proactor 管理。当定时器到期时,超时机制使用PostQueuedCompletionStatus()函数来将“完成”投递给前摄器的 I/O 完成端口。这一设计将定时器机制与普通的完成分派机制干净地集成在一起,并且还确保了只有一个线程会醒来分派定时器,因为所有的完成检测线程都只等待完成事件,而无需担心会因为已调度的定时器到期而苏醒过来。
ACE_POSIX_Practor 类
在 POSIX 系统上的 ACE Proactor 实现给出了多种用于发起 I/O操作及检测其完成的机制。此外Sun 的 Solaris OperatingEnvironment 提供了它自己的异步IO私有版本。在 Solaris 2.6 及更高的版本上,Sun特有的并步I/O函数的性能要显著地高于Salaris的 POSIX.4 AIO 实现的性能。为了利用这一性能提高,ACE还在一组 组另外的类中封装了这--机制。
实现综述。异步 I/O的 POSIX 实现使用了控制决(smnuct aiocb)来标识每个异步 I/O 请求及它的控制信息。每个aiocb同时只能与一个I/O请求相关联。Sun特有的异步I/O使用了额外的叫做aio.resultt的结构。
尽管被封装的 POSIX 异步 I/O机制支持 read()和 write()操作,它们不支持任何与 TCP/IP 连接有关的操作。为了支持 ACE_Asynch_Acceptor和ACE_Asynch_Connector的各个函数,我们使用了另外一个线程来执行与连接有关的操作。下表展示了ACE_POSIX_Practor的三个变种:
并发考虑事项。在支持POSIX.4 AIO的平台上,ACE_POSTX_SIG_Proactor是缺省的前摄器实现其完成事件多路分离机制使用的是sigtimedwai()函数,每个ACE_POSTX_SIG_Proactor实例都可以指定用于 sigtimedwait()的信号集。因此,为了在不同的优先级上将多个线程用于不同的ACE_POSTX_SIG_Proactor实例,每个实例应该使不同的信号或信号集。
有些平台的局限和特征会直接影响所能使用的ACE_POSIX_Proactor。例如,在Linux上,线程实际上是克隆的进程。因为无法跨越进程发送信号,而异步I/O操作和Proactor定时器到期都是使用线程来实现的,在linux上ACE_POSTX_SIG_Proactor无法良好地工作。因此,在Linux上,ACE_POSIX_AIOCB_Proactor 是缺省的前摄器实现。在ACE_POSIX_AIOCB_Proactor 中使用的aio_suspend()多路分离机制是线程安全的,所以多个线程可以同时运行其事件循环。
下面将通过一个具体的示例来详细介绍:
AIO_Output_Handler::handle_read_stream()方法的讨论中提到了
AIO_CLD_Connector重新连接机制。该机制是使用下面的AIO_CLD_Connector:reconnect()方法来发起的:
int reconnect(void){return connect(remote_addr_);}
这个方法简单地发起一个新的异步连接请求给服务器日志daemon。在其中使用了一种指数后退策略来避免在某些异常情况下持续地发起连接尝试,例如,没有服务器日志daemon在侦听。我们使用了下面的 validale_connection()挂钩方法来将成用定义的行为插入ACE_Asynch_Connector的连接完成处理中。这个方法了解每个异步连接请求的部署,并调度定时器来在其失败时重新尝试连接。如果连接成功,这个方法执行与服务器日志daemon的SSL认证。
int AIO_CLD_Connector::validate_connection
(const ACE_Asynch_Connect::Result& result,
const ACE_INET_Addr &remote, const ACE_INET_Addr&) {
//保存尝试连接的对端信息 在未来的复连中进行复用
remote_addr_ = remote;
//如果连接失败 设置一个定时器 以便后面重新尝试连接
if (!result.success ()) {
ACE_Time_Value delay (retry_delay_);
retry_delay_ *= 2; //重连时间加倍
if (retry_delay_ > MAX_RETRY_DELAY) //直到达到MAX_RETRY_DELAY
retry_delay_ = MAX_RETRY_DELAY;
proactor ()->schedule_timer (*this, 0, delay);
return -1;
}
//连接成功 复位为初始值
retry_delay_ = INITIAL_RETRY_DELAY;
if (ssl_ctx_ == 0) {
//初始化OpenSSL库
OpenSSL_add_ssl_algorithms ();
//设置SLL版本3连接,并创建于要认证的连接相应的SSL结构
ssl_ctx_ = SSL_CTX_new (SSLv23_client_method ());
if (ssl_ctx_ == 0) return -1; //认证失败返回-1
//设置用于在建立连接标识服务器的证书和附随的私钥,并随机认证与正确的证书是匹配的
//这些代码和证书是以pem格式编码放在指定文件中的
if (SSL_CTX_use_certificate_file (ssl_ctx_,
CLD_CERTIFICATE_FILENAME,
SSL_FILETYPE_PEM) <= 0
|| SSL_CTX_use_PrivateKey_file (ssl_ctx_,
CLD_KEY_FILENAME,
SSL_FILETYPE_PEM) <= 0
|| !SSL_CTX_check_private_key (ssl_ctx_)) {
SSL_CTX_free (ssl_ctx_);
ssl_ctx_ = 0;
return -1;
}
//设置一个新的SSL结构。在通过OpenSSL API建立的SSL连接时
ssl_ = SSL_new (ssl_ctx_);
if (ssl_ == 0) {
SSL_CTX_free (ssl_ctx_); ssl_ctx_ = 0;
return -1;
}
}
//复位SSL数据结构 以用于新的SSL连接
SSL_clear (ssl_);
#if defined (ACE_WIN32)
// ACE_WIN32 is the only platform where ACE_HANDLE is not an int.
// See ace/config-lite.h for the typedefs.
SSL_set_fd (ssl_, reinterpret_cast<int> (result.connect_handle ()));
#else
SSL_set_fd (ssl_, result.connect_handle ());
#endif /* ACE_WIN32 */
//配置SSL结构,以在接收SSL连接时强制实施客户认证
SSL_set_verify (ssl_, SSL_VERIFY_PEER, 0);
//进行实际的SSL连接,如果连接失败 SSL_connect 也失败
if (SSL_connect (ssl_) == -1
|| SSL_shutdown (ssl_) == -1) return -1;
return 0;
}
当 handle_connection()所设置的定时器到期时,ACE Proactor 调用下面的 handle_time_out()井钩方法:
void AIO_CLD_Connector::handle_time_out
(const ACE_Time_Value&, const void *) {
this->connect (remote_addr_);
}
这个方法简单地发起另一次异步 connect()尝试,该尝试将触发对validate_connection()的另一次调用,但并不管它是成功还是失败。
接下来介绍AIO客户日志daemon服务由下面的AIO_Client_Logging_Daemon类进行表示:
class AIO_Client_Logging_Daemon : public ACE_Task<ACE_NULL_SYNCH> {
public:
// Service Configurator hook methods.
virtual int init (int, ACE_TCHAR *[]);
virtual int fini ();
//virtual int svc(); 该函数在ACE_Task中继承而来 在AIO_Client_Logging_Daemon 中实现
};
这个类与AC_Client_Logging_Daemon 类似:主要的不同是AIO_Client_Logging_Daemon会派生一个新线程来运行服务所依赖的前摄器事件循环,而AC_Client_Logging_Daemon 依赖于主程序线程运行单体反应器的事件循环。为了易于激活这个线程AIO_Client_Logging_Daemon派生自ACE_Task,而不是ACE_Service_Config。
我们为前摄器启用一个新线程,是因为我们仍然要依赖反应器事件循环来进行服务重配置活动。如果我们的服务只是为Windows设计的,将前摄器和反应器集成在一起。但是,对于所有支持AIO的ACE平台来说,这个客户日志daemon实现都是可移植的。在处理了参数列表、并形成了地址之后,我们的AIO_Client_Logging_Daemon:init()方法随即调用 ACE_Task::activate()来派生一个线程,运行下面的svc()方法:
int AIO_Client_Logging_Daemon::svc () {
//初始化acceptor_ 开始侦听日志客户连接 并发起第一次连接尝试
if (acceptor_.open (cld_addr_) == -1) return -1;
if (CLD_CONNECTOR::instance ()->connect (sld_addr_) == 0)
//调用proactor_run_event_loop处理异步操作完成 最后循环通过fini进行关闭
ACE_Proactor::instance ()->proactor_run_event_loop ();
//关闭打开的连接和单体对象 并退出svc线程
acceptor_.close ();
CLD_CONNECTOR::close ();
OUTPUT_HANDLER::close ();
return 0;
}
调用proactor_run_event_loop处理异步操作完成 最后循环通过fini进行关闭
int AIO_Client_Logging_Daemon::fini () {
ACE_Proactor::instance ()->proactor_end_event_loop ();
this->wait ();
return 0;
}
fini方法调用了proactor_end_event_loop 来结束svc方法中的事件循环,随即使用ACE_TASK::wait()等待svc方法完成和退出线程。
最后,需要增加必要的ACE_FACTORY_DEFINE宏来生成服务的工厂函数:
ACE_FACTORY_DEFINE (AIO_CLD, AIO_Client_Logging_Daemon)
新前摄式客户日志daemon服务使用了ACE Servicc Configurator框架来将其自身配置进任何主程序中,比如通过在svc.conf文件中包括下面的条目来配置进 Confgurabie_Logging_Server中:
dynamic AIO_Client_Logging_Daemon Service_Object *
AIO_CLD:make_AIO_Client_Logging_Daemon()
"-p $CLIENT_LOGGING_DAEMON_PORT"
Proactor 模式(异步网络模式)定义了一组角色和关系来帮助简化使用前摄式IO的应用。ACE Proactor框架跨越一系列支持异步I/O的操作系统实现了 Proactor 模式。该框架提供的一组类简化了网络化应用跨越所有提供异步I/O能力的平台、对这些能力的使用。本文章讨论了框架中的各个类,涵盖了它们的动机和能力。本章还描述了一个客户日志daemon的实现,将前摄式I/O 模型用于其所有网络操作,这一版本的客户日志 daemon能够在所有提供了异步 I/O 机制的 ACE 平台上可移植地工作。
ACE Streams框架
接下来将介绍ACE Streams框架,该框架实现了Pipes and Filters(管道与过滤器)模式,这是一种为处理数据流的系统提供结构的架构型模式。我们将阐释怎样使用ACE Streams框架来开发一个实用程序,对我们的日志服务器所存储的日志记录文件进行格式化和打印。
Pipes and Filers 架构型模式是一种组织分层式/模块化应用的常用方式。这种模式为数据流处理提供了一种架构,在其中每个处理步骤被封装在某种类型的过滤器组件中。数据通过某种通信机制在相邻的过滤器之间传递,这些机制的范围从连接本地或远地进程的IPC 信道直到在同一进程中引用对象的简单指针。每个过滤器可以在将数据向前传递给下个过滤器之前增加、修改、或者是移除数据。过滤器常常是无状态的,也就是说,通过某个过滤器的数据被转换,并向前传给下一个过滤器,而不会被存储。
Pipes and Filters 模式的常见例子包括:
1、UNIX shell 用于创建单向管线(Pipeline)的 UNIX管道IPC 机制。
2、System V STREAMS 这种机制提供了一种用于把双向协议集成进 UNIX内核的框架。
ACE Streams 框架基于 Pipes and Filters 模式。这个框架简化了分层式/模块化用的开发,这些应用可以通过双向处理模块来进行通信。本文主要介绍以下类:
上图显示了 ACE Streams 框架中的各个类之间最为重要的关系,这些类依照 Pipes and Filter模式扮演了以下角色:
过滤器类 它是流中的处理单元,对输入数据进行丰富、精炼或转换(。ACE Streams 框架中的过滤器类: ACE Task 的子类实现。
管道类 表示过滤器之间的互连。ACE Steams 框架中的管道类是出 ACE Module 对象提供的,
这些对象含有互连的 ACE Task对象,它们链接在一起,以此来创建完整的双向 ACE Stream。
在相邻的互连任务中应用所定义的方法通过交换数据和控制消息块来进行协作。
ACE Streams 框架主要提供了以下好处:
增强复用和灵活性 可以将任何ACE_Task插入任何 ACE Module 中,也可将任何 ACE_Module插入任何ACE_Sream中。这一灵活性赋予了多个应用系统地复用已有的模块和任务的能力,这些模块和任务都可被动态配置。
透明、递增的演化 通过增加、移除以及改变模块和任务,可以以一种受控的方式来实现应用功能。在敏捷开发过程中--比如Extremc Programming(XP)----这样的演化应用设计和功能的能力特别有用
宏一级的性能调整 通过有选择地省略不必要的服务功能或足重新配置ACE_Task 和ACE_Module 对象的混成物,应用可以适配到各种部署情况和运行时环境,从而在特定环境中提供最优化的服务功能。
固有的模块性 ACE Steams 框架强制建立的结构促进了已被广泛接受的设计实践:内聚和最小耦合。模块化设计有助于降低各层次的复杂度并改善网络化应用和服务的总体实现。基于高度模块化的设计,更易于完成测试和文档建立也能使应用维护和系统化复用变得更为容易。
前面发布的文章已经详细介绍了ACE_Task,这里则主要介绍ACE_Model及ACE_Stream类提供的能力。
ACE_Model类
ACE_Task 类提供的可复用组件可以很容易地被用于将处理划分为多个阶段,并在其间传递数据。但是,因为 ACE_Task 对象是独立的,我们需要增加额外的结构来按学排列 ACE_Task 对象,使其成为双向的、可作为单元来装配和管理的“reader-writer”(读取者一写入者)对。在多个项日中重复开发这一结构既繁琐,又没有必要,因为该结构在根本上是不依赖于应用的。因此,为了避免进行这一多余的开发工作,ACE Streams 架定义了 ACE_Module 类。ACE_Module定义了一个清晰可辨的、由应用定义的功能层。这个类提供了以下能力:
其关键方法如下:
template <ACE_SYNCH_DECL, class TIME_POLICY = ACE_System_Time_Policy>
class ACE_Module : public ACE_Module_Base
{
public:
/// Create an empty Module.
ACE_Module ();
/// Shutdown the Module.
virtual ~ACE_Module ();
/// Create an initialized module with @a module_name as its identity
/// and @a reader and @a writer as its tasks.
ACE_Module (const ACE_TCHAR *module_name,
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *writer = 0,
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *reader = 0,
void *args = 0,
int flags = M_DELETE);
/**
* Initialize the module with @a module_name as its identity
* and @a reader> and @a writer as its tasks. Previously register
* reader or writers or closed down and deleted according to the
* value of flags_. Should not be called from within
* <ACE_Task::module_closed>.
*/
int open (const ACE_TCHAR *module_name,
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *writer = 0,
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *reader = 0,
void *a = 0,
int flags = M_DELETE);
/**
* Close down the module and its tasks. The flags argument can be
* used to override the default behaviour, which depends on previous
* @a flags values in calls to c'tor, <open>, <reader>, and <writer>.
* A previous value M_DELETE[_XXX] can not be overridden. Should
* not be called from within <ACE_Task::module_closed>.
*/
int close (int flags = M_DELETE_NONE);
// = ACE_Task manipulation routines
/// Get the writer task.
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *writer ();
/**
* Set the writer task. @a flags can be used to indicate that the
* module should delete the writer during a call to close or to the
* destructor. If a previous writer exists, it is closed. It may
* also be deleted, depending on the old flags_ value. Should not
* be called from within <ACE_Task::module_closed>.
*/
void writer (ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *q, int flags = M_DELETE_WRITER);
/// Get the reader task.
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *reader ();
/**
* Set the reader task. @a flags can be used to indicate that the
* module should delete the reader during a call to close or to the
* destructor. If a previous reader exists, it is closed. It may
* also be deleted, depending on the old flags_ value. Should not
* be called from within <ACE_Task::module_closed>.
*/
void reader (ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *q, int flags = M_DELETE_READER);
/// Set and get pointer to sibling ACE_Task in an ACE_Module
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *sibling (ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *orig);
// = Identify the module
/// Get the module name.
const ACE_TCHAR *name () const;
/// Set the module name.
void name (const ACE_TCHAR *);
// = Argument to the Tasks.
/// Get the argument passed to the tasks.
void *arg () const;
/// Set the argument passed to the tasks.
void arg (void *);
/// Link to other modules in the ustream stack
void link (ACE_Module<ACE_SYNCH_USE, TIME_POLICY> *m);
/// Get the next pointer to the module above in the stream.
virtual ACE_Module<ACE_SYNCH_USE, TIME_POLICY> *next ();
/// Set the next pointer to the module above in the stream.
virtual void next (ACE_Module<ACE_SYNCH_USE, TIME_POLICY> *m);
/// Dump the state of an object.
void dump () const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
private:
/// Implements the close operation for either the reader or the
/// writer task (depending on @a which).
int close_i (int which, int flags);
/// Pair of Tasks that form the "read-side" and "write-side" of the
/// ACE_Module partitioning.
ACE_Task<ACE_SYNCH_USE, TIME_POLICY> *q_pair_[2];
/// Name of the ACE_Module.
ACE_TCHAR name_[MAXPATHLEN + 1];
/// Next ACE_Module in the stack.
ACE_Module<ACE_SYNCH_USE, TIME_POLICY> *next_;
/// Argument passed through to the reader and writer task when they
/// are opened.
void *arg_;
/// Holds flags which are used to determine if the reader and writer
/// task have to be deleted on exit
int flags_;
};
对ACE_Task 类所提供的面向对象的处理抽象进行特殊化,以瞄准任何特定的应用领域,比如网络协议栈或客户服务呼叫中心管理。ACE_Task消息传递和排队机制提供了一种直截了当的方式来将某个领域的工作划分进多个不同的步骤中,并在它们之问高效地进行双向的工作和数据移动。对于被读取和写入的数据,许多领域具有对称的处理步骤。例如,协议处理常常涉及这样任务,它们被用于“校验和应用安全转换”,比如加密。ACE_Module 提供了一种统一而灵活的组合机制,可将这些应用定义的ACE_Task 对象的实例关联进:
1、一个“reader”端,处理“溯流而上”(Upstream)发送到此 ACE_Module 层的消息
2、一个“writer”端,处理“顺流而下”(Downstream)发送到此 ACE Module 层的消息
组成某个双向模块的两个任务被称为“兄弟”。例如,在加密的例子中,reader 任务将校验并解密接收到的数据,而它的“兄弟”writer任务将在写出数据之前对其进行加密。一个ACE_Module 可以由这两个任务组合而成,并被适当地配置进流中。
在某一层只在一个方向上活跃地处理数据的情况下,不活跃的兄弟任务可被指定为NULL 指针,从而促使 ACE_Module 安装一个 ACE_Thru_Task,这种任务只是简单地将所有消总转发给下一个模块的任务,而不对它们进行修改。即使是在各层被重新排列、增加或删除的情况下,这一设计也能够维持分层结构。
下面将介绍一个display_logfile的使用程序,它读取我们的日志服务器所存储的日志记录,对信息进行格式化,并以人能够阅读的格式打印出来,为了实现 display_logfile程序,我们实现了以下派生门 ACE_Modute 的类:
1、Logrec Reader 模块将日志文件中的日志记录转换为一种规范的格式,并随即传给 ACE_Stream中的其他模块作处理。
2、Logrec Formatter 模块决定怎样格式化日志记录中的各个域,例如,将它们从二进制转换为
ASCII.
3、Logrec Separator 模块在复合日志记录消息中的己有消息块之间插入含有分隔符字符串的消息块。
4、Logrec Writer 模块将已格式化的日志记录消息打印到标准输出,在那出它们可被重定向到文件打印机或是控制台。
下图说明了组成 display_logfile 程序的 ACE_Steam 中的各模块的结构
这个程序使用了生产者/消费者并发模型,在其中Logrec_Reader 和 Logrcc_Writer 作为主动对象运行,分别生产和消费消息。上图还说明了 Logrec_Reader 模块所创建的发合ACE_Message_Block的结构;ACE Stream中的其他过滤器模块也会对这种消息块进行操作。
在下面的示例中,我们从 ACE_Task和 ACE_Module 继承,创建了 Logrec Reader、LogrecFormatter、Logrec Separator,以及 Logrec Writer 模块。我们使用了下面的 Logrec_Module 模板利LOGREC_MODULE宏来简化后面的许多示例:
template <class TASK>
class Logrec_Module : public ACE_Module<ACE_SYNCH>
{
public:
Logrec_Module (const ACE_TCHAR *name)
{
this->open (name,
&task_, // Initialize writer-side task.
0, // Ignore reader-side task.
0,
ACE_Module<ACE_SYNCH>::M_DELETE_READER);
}
private:
TASK task_;
};
#define LOGREC_MODULE(NAME) \
typedef Logrec_Module<NAME> NAME##_Module
因为消息流是单向的(从Logrec Reader 到 Logrec Writer),我们在 ACE_Module 构造器中以对writer 端任务进行了初始化。通过把 NULL指针传给reader 端任务参数,ACE_Module 构造器将创建ACE_Thru_Task 的实例来转发所有的消息,并且不对它们进行修改。M_DELETE_READER 标志指示 ACE_Moduke 析构器,只删除reader 端任务,而不要删除 writer 端任务。
Logrec_Reader_Module。这个模块含有 Logrec_Reader 任务类的一个实例,该实例进行以下活动,功能如下所示:
class Logrec_Reader_Module : public ACE_Module<ACE_SYNCH>
{
public:
Logrec_Reader_Module (const ACE_TString &filename)
: task_ (filename)
{
this->open (ACE_TEXT ("Logrec Reader"),
&task_, // Initialize writer-side.
0, // Ignore reader-side.
0,
ACE_Module<ACE_SYNCH>::M_DELETE_READER);
}
private:
Logrec_Reader task_;
};
1、它打开指定的日志文件。
2、它将 Logrec_Reader 实例激活为主动对象,
3、它将日志文件中的日志记录转换为一组串联的消息块,每个块含有一个域,其内容来自解除了整编的日志记录;ACE_Stream中的后续模块随后会对消息块进行处理。
Logrec_Reader类如下所示:
class Logrec_Reader : public ACE_Task<ACE_SYNCH>
{
private:
ACE_TString filename_; // Name of logfile.
ACE_FILE_IO logfile_; // File containing log records.
public:
//定义了五个枚举量来标识日志记录中的各个域
//没有将域指示存储在数据自身中 而是通过ACE_Message_Block type成员来指定
enum {MB_CLIENT = ACE_Message_Block::MB_USER,
MB_TYPE, MB_PID, MB_TIME, MB_TEXT};
Logrec_Reader (const ACE_TString &file): filename_ (file) {}
//FUZZ: disable check_for_lack_ACE_OS
// 打开指定日志文件 并将任务转换为主动对象
virtual int open (void *) {
//FUZZ: enable check_for_lack_ACE_OS
ACE_FILE_Addr name (filename_.c_str ());
ACE_FILE_Connector connector;
if (connector.connect (logfile_, name) == -1)
return -1;
return activate (); //转换为主动对象
}
/*
Logrec_Reader::svc()运行在主动对象线程中。它从日志文件中读取日志记录,解除每个记录的整编,将 其存储在复合消息块中,并将每个复合消息块传向流的上游,以出其模块作进-步处理。日志文件包含一系列日 志记录,每个记录由以下两项组成:
1、一个含有发送日志记录的客户名称的字符串
2、一个经过 CDR 编码的 ACE_Log_Record
每个日志记录紧跟着前面的记录,记录间没有标记。如下所示,Logrec_Reader::svc()将文件内容读入大的 chunk,并将它们作为数据流来解除整编。外层for循环读取文件内容,直至遇到 EOF。内层for循环解除来自数据 chunk 的口志记录的整编。
*/
virtual int svc () {
//设置消息块大小
const size_t FileReadSize = 8 * 1024;
ACE_Message_Block mblk (FileReadSize);
//使用crunch 将任何在mblk中的数据移位到数据缓冲区的开头
//腾出空间用于追加更多的数据
for (;; mblk.crunch ()) {
// Read as much as will fit in the message block.
//开始循环 将文件内容读取到ACE_Message_Block 中
ssize_t bytes_read = logfile_.recv (mblk.wr_ptr (),
mblk.space ());//space()查看块中有多少自由空间可用
if (bytes_read <= 0)
break;
mblk.wr_ptr (static_cast<size_t> (bytes_read));
//开始日志解除整编循环
for (;;) {
size_t name_len = ACE_OS::strnlen //获取主机名字名字字符串长度
(mblk.rd_ptr (), mblk.length ());
if (name_len == mblk.length ()) break; //只查看mblk的剩余字符 没有则返回
char *name_p = mblk.rd_ptr (); //记住开始指针name_p 用于重新开始整编
ACE_Message_Block *rec, *head, *temp;
ACE_NEW_RETURN
(head, ACE_Message_Block (name_len, MB_CLIENT), 0);
head->copy (name_p, name_len); //使用head消息块存放主机名
mblk.rd_ptr (name_len + 1); // Skip nul also
//mblk位于CDR编码的ACE_LOG_Record的起始处
size_t need = mblk.length () + ACE_CDR::MAX_ALIGNMENT;
ACE_NEW_RETURN (rec, ACE_Message_Block (need), 0); //申请一个新的ACE_Message_Block 用于对其
ACE_CDR::mb_align (rec); //进行字节对其
rec->copy (mblk.rd_ptr (), mblk.length ()); //对其后复制剩余的内容
//创建ACE_InputCDR 对象来解除rec中日志内容的整编
//ACE_InputCDR 构造器增加了rec的计数 rec为对其后的ACE_Message_Block
ACE_InputCDR cdr (rec); rec->release ();//调用release释放其引用 防止内存泄漏
ACE_CDR::Boolean byte_order;
if (!cdr.read_boolean (byte_order)) {//被解除第一项是字节序指示符 用于复位cr的字节序
head->release (); rec->release (); break;
}
cdr.reset_byte_order (byte_order);
//为已整编的ACE_Log_Record所写的头中包含一个长度域
//指明已整编的日志记录字节数
ACE_CDR::ULong length;
//如果已经到场的还不是所需字节
if (!cdr.read_ulong (length)) {
head->release (); mblk.rd_ptr (name_p); break;//释放head 复位mblk指针
}
if (length > cdr.length ()) {
//以在下一遍整编时重新从主机名开始
//并跳出记录 解除整编循环 从文件中读取更多的数据
head->release (); mblk.rd_ptr (name_p); break;//释放head 复位mblk指针
}
//为余下的所以日志分配消息块
//每个块都拥有正确的块类型 以在流的下游模式正确地进行标识
ACE_NEW_RETURN (temp,
ACE_Message_Block (length, MB_TEXT),
0);
ACE_NEW_RETURN
(temp,
ACE_Message_Block (2 * sizeof (ACE_CDR::Long),
MB_TIME, temp),
0);
ACE_NEW_RETURN
(temp,
ACE_Message_Block (sizeof (ACE_CDR::Long),
MB_PID, temp),
0);
ACE_NEW_RETURN
(temp,
ACE_Message_Block (sizeof (ACE_CDR::Long),
MB_TYPE, temp),
0);
head->cont (temp); //通过cont直接连接起来
//将消息块强转为ACE_CDR::Long *
ACE_CDR::Long *lp;
lp = reinterpret_cast<ACE_CDR::Long*> (temp->wr_ptr ());
cdr >> *lp;//使用CDR提取操作符解除类型域的整编 解除后的整编放到第一个消息块中
temp->wr_ptr (sizeof (ACE_CDR::Long));
temp = temp->cont ();//调整写的指针,移动链中的下一个消息块
//使用同样的技术解析进行ID和时间戳的整编
lp = reinterpret_cast<ACE_CDR::Long*> (temp->wr_ptr ());
cdr >> *lp;
temp->wr_ptr (sizeof (ACE_CDR::Long));
temp = temp->cont ();
lp = reinterpret_cast<ACE_CDR::Long*> (temp->wr_ptr ());
cdr >> *lp; ++lp; cdr >> *lp;
temp->wr_ptr (2 * sizeof (ACE_CDR::Long));
temp = temp->cont ();
//日志记录的数据部分之前被整编为进行计数的字节序列
//解除序列长度的整编 随后解除各字节自身的整编
ACE_CDR::ULong text_len;
cdr >> text_len;
cdr.read_char_array (temp->wr_ptr (), text_len);
temp->wr_ptr (text_len); //放到联众最后的消息块
//将消息块传到流中的下一个模块进一步处理
if (put_next (head) == -1) break;
//向前移动文件内容决的读指针,越过刚刚提取的所有数据。
//作为CDR操作的结果,cur对象一直在调整其内部的指针,所以 length()方法指示剩下多少数据。
//因为起始长度与 mblk.length()(这个长度在整个解除整编过程中都没有被调整)相同,
//可以确定已经消费了原来的消息块的多少内容。
mblk.rd_ptr (mblk.length () - cdr.length ());
}
}
//整个文件已被处理 所以向流的下游发送一个大小为0 类型为MB_STOP的消息块
//按照约定 这个消息块指示流中的其他模块会停止他们的处理
ACE_Message_Block *stop;
ACE_NEW_RETURN
(stop, ACE_Message_Block (0, ACE_Message_Block::MB_STOP),
0);
put_next (stop);
return 0;
}
};
因为日志文件的名称被传给了 Logrec_Read_Module 构造器,我们不能使用 LOGREC_MODULE宏。相反,我们显式地定义这个类,如下所示:
class Logrec_Reader_Module : public ACE_Module<ACE_SYNCH>
{
public:
Logrec_Reader_Module (const ACE_TString &filename)
: task_ (filename)
{
this->open (ACE_TEXT ("Logrec Reader"),
&task_, // Initialize writer-side.
0, // Ignore reader-side.
0,
ACE_Module<ACE_SYNCH>::M_DELETE_READER);
}
private:
Logrec_Reader task_;
};
Logrec_Foratter_Module。这个模块包含的Logrec_Formatter 任务决定日志记录域的格式化方式,如下所示:
class Logrec_Formatter : public ACE_Task<ACE_SYNCH>
{
//日志区域格式化方式
public:
typedef void (*FORMATTER[5])(ACE_Message_Block *);
private:
static FORMATTER format_; // Array of format static methods.
public:
//通过put会借用其调用者的线程来格式化信息
virtual int put (ACE_Message_Block *mblk, ACE_Time_Value *) {
//确定转发的消息类型
//如果为MB_CLIENT 则是串联起来的含有日志记录域中的第一个块
if (mblk->msg_type () == Logrec_Reader::MB_CLIENT)
//从头到尾跌倒出所有的记录域
for (ACE_Message_Block *temp = mblk;
temp != 0;
temp = temp->cont ()) {
int mb_type =
temp->msg_type () - ACE_Message_Block::MB_USER;
(*format_[mb_type])(temp); //转化为可读格式
}
return put_next (mblk); //传给下一个模块
}
//使用静态方法对相应类型进行初始化
//MB_CLIENT块中有一个长度已知的文本串 无需进一步格式化 直接返回即可
static void format_client (ACE_Message_Block *) {
return;
}
//将日志类型转换为ASCII 表示
static void format_type (ACE_Message_Block *mblk) {
ACE_CDR::Long type = * (ACE_CDR::Long *)mblk->rd_ptr ();
mblk->size (11); // Max size in ASCII of 32-bit word.
mblk->reset ();// 将读指针和写指针初始化到内存缓冲区的起始处
mblk->wr_ptr ((size_t) ACE_OS::sprintf (mblk->wr_ptr (), "%d", type));/进行格式化处理
}
//进程id转换为ASCII 表示
static void format_pid (ACE_Message_Block *mblk) {
ACE_CDR::Long pid = * (ACE_CDR::Long *)mblk->rd_ptr ();
mblk->size (11); // 确保有足够的空间
mblk->reset (); // 将读指针和写指针初始化到内存缓冲区的起始处
mblk->wr_ptr ((size_t) ACE_OS::sprintf (mblk->wr_ptr (), "%d", pid));//进行格式化处理
}
//将事件的分秒转换为ASCII 字符串
static void format_time (ACE_Message_Block *mblk) {
ACE_CDR::Long secs = *(ACE_CDR::Long *) mblk->rd_ptr ();//获取秒
mblk->rd_ptr (sizeof (ACE_CDR::Long));
ACE_CDR::Long usecs = *(ACE_CDR::Long *) mblk->rd_ptr ();//获取微秒
ACE_TCHAR timestamp_t[26];
char timestamp[26]; // Max size of ctime_r() string.
time_t time_secs (secs);
ACE_OS::ctime_r (&time_secs,
timestamp_t,
sizeof timestamp_t / sizeof (ACE_TCHAR));
ACE_OS::strcpy (timestamp, ACE_TEXT_ALWAYS_CHAR (timestamp_t));
mblk->size (26); // Max size of ctime_r() string.
mblk->reset ();
timestamp[19] = '\0'; // NUL-terminate after the time.
timestamp[24] = '\0'; // NUL-terminate after the date.
size_t fmt_len (ACE_OS::sprintf (mblk->wr_ptr (),
"%s.%03d %s",
timestamp + 4,
usecs / 1000,
timestamp + 20));
mblk->wr_ptr (fmt_len);
}
//与format_client 一样 已知长度 直接返回
static void format_data (ACE_Message_Block *) {
return;
}
};
通过lamda表达式来初始化各个格式化的指针的数组:
Logrec_Formatter::FORMATTER Logrec_Formatter::format_ = {
format_client, format_type, format_pid, format_time, format_data
};
使用了指向静态方法的指针,而不是指向非静态方法的指针,因为这些方法尤需访问Logrec_Foratter状态。
接下来,我们通过Logrec_Formarer类来实例化LOGREC_MODULE宏,从而创建Logrec_Formatter_Module:
LOGREC_MODULE (Logrec_Formatter);
Logrec_Separator_Module。
这个模块含有个Logrec_Separator 对象,负责在某个复合日志记录消息中的已有的消息块之间插入消息块。各个新插入的消息块中含有分隔符宁符串。
class Logrec_Separator : public ACE_Task<ACE_SYNCH>
{
private:
ACE_Lock_Adapter<ACE_SYNCH_MUTEX> lock_strategy_;
public:
//借用了调用者的线程来插入分隔符
virtual int put (ACE_Message_Block *mblk,
ACE_Time_Value *) {
if (mblk->msg_type () != ACE_Message_Block::MB_STOP) {//如果是MB_STOP 转发给下一个流模块
//创建一个消息来存放分隔符字符串 "|"
ACE_Message_Block *separator;
ACE_NEW_RETURN
(separator,
ACE_Message_Block (ACE_OS::strlen ("|") + 1,
ACE_Message_Block::MB_DATA,
0, 0, 0, &lock_strategy_),
-1);
separator->copy ("|");
//针对消息块列表进行循环 在原来的基础上添加含有分割符的separator复本
ACE_Message_Block *dup = 0;
for (ACE_Message_Block *temp = mblk; temp != 0; ) {
dup = separator->duplicate ();
dup->cont (temp->cont ());
temp->cont (dup);
temp = dup->cont ();
}
//追加换行字符串
ACE_Message_Block *nl;
ACE_NEW_RETURN (nl, ACE_Message_Block (2), 0);
nl->copy ("\n");
//释放原来的字符串 将修改过的复合消息传送给流中的下一个模块
dup->cont (nl);
separator->release ();
}
return put_next (mblk);
}
};
现在通过Logrec_Separator类来实例化LOGREC_MODULE宏,从而创建Logrec_Separator_Module:
LOGREC_MODULE (Logrec_Separator);
Logrec_Writer_Module。这个模块含有一个进行以下活动的LogRec_Writer 任务:
1、它将 Logrec_Writer 实例激活为主动对象。
2、它接收相邻模块传来的已格式化的日志记录消息,并将它们打印到它的标准输出。
class Logrec_Writer : public ACE_Task<ACE_SYNCH> //利用ACE_Task 通过ACE_SYNCH trait实例类
{
public:
//FUZZ: disable check_for_lack_ACE_OS
// Initialization hook method.
//挂钩方法转换为主动对象
virtual int open (void *) { return activate (); }
//FUZZ: enable check_for_lack_ACE_OS
//将消息放入到同步队列中
virtual int put (ACE_Message_Block *mblk, ACE_Time_Value *to)
{ return putq (mblk, to); }
//运行在主动对象线程中 从消息队列中取出消息 并写到标准输出
virtual int svc () {
int stop = 0;
for (ACE_Message_Block *mb = 0; !stop && getq (mb) != -1; ) {
if (mb->msg_type () == ACE_Message_Block::MB_STOP)//收到MB_STOP信号 跳出循环并返回
stop = 1;
else
ACE::write_n (ACE_STDOUT, mb);//写到标准输出中
put_next (mb);
}
return 0;
}
};
最后,我们通过Logrec_Witer类来实例化LOGREC_MODULE宏,从而创建Logrec_Writer_Module:
LOGREC_MODULE (Logrec_Writer);
ACE_Streams类
ACE_Stream 实现了 Pipes and Filters 模式,以使开发者能够通过定制可复用的、不依赖于应用的框架类,配置和执行按层次关联的服务。这个类提供了以下能力:
ACE_Stream 类输出了ACE Streams框架的许多特性,将其方法分为三个范畴中, 其主要接口如下所示:
1、流初始化和析构方法 下面的方法初始化和销毁ACE_Stream 对象
在缺省情况下,ACE创建的两个ACE_Modue对象在 ACE_Stream 被初始化时被安装进其中,一个在流的头部,另一-个在尾部。头模块在其reader 和 wrier 端都含有 ACE_Stream_Head 对象。同样,尾模块在 reader 和 writer 端都含有 ACE_Stream_Tail 对象。这两个类会解释预先定义的、可在运行时环流 ACE_Stream 的控制消息和数据消息块。ACE_Message_Block 类将这些消息块的类型定义为枚举符,其值全都小于 MB_USER。
ACE_Stream_Head 可以在应用和 ACE_Stream 的实例之间对消息进行排队。当消息被传给顶部模块的writer端ACE_Stream_Head时,它被转发给流中的下一个模块。当消息被传给顶部模块的reader端ACE_Steam_Head时,它被放入任务的消息队列中。
为了在消息没有被处理而到达流的术端的情况下防止资源泄漏,ACE_Stream_Tail::put()在充当writer时会释放它所接收到的消息。这一行为使得Logrec_Witer模块可以将其消息块传给下一个任务。下一个任务很可能是一个ACE_Stream_Tail。但是,如果对程序进行扩展,在流中的该点上增加另一个模块,该模块仍然会如我们所期望的那样工作。
2、流配置方法 下面的方法将ACE_Module象推入和弹出某个ACE_Stream:
push()和 pop()方法允许应用在运行时通过“在流的顶部插入或移除 ACE_Module 的实例”来对流进行配置。当模块被推入流中时,模块的writer和reader 端任务的open()挂钩方法会被自动调用,同样,当模块被从流中弹出时,其wrier和reader任务的close()挂钩方法会被满用。
因为一个完整的流被表示为一系列互连的独立模块,流中的行意一点插入、移除,以及替模块常常很有用。因此,为了支持这些操作,ACE_Module 对象任流中是有名字的,而insert()、replace()和remove()方法可以操作流的各个模块。在本质上,可以通过任何满足应用需求并增强模块复用的配置来生成 ACE_Module 对象。
3、流通信模块 下面的方法被用于在ACE Streaml:发送和接收消息:
get()和 put()方法允许应用在ACE_Stream 对象上发送和接收消息。与在 ACE_Message_Queue 中插入消息块类似,可以通过传递以下类型(见表9.6)的ACE_Time_Value 值来改变这些方法的阻塞行为:
当消息块到达某个 ACE_Stream 时,它们可以通过调用 put_next()方法来穿过一系列互连的 ACE_Task对象。消息块可以来自各种来源,比如应用线程、反应器或前摄器,或是 ACE定时器队列分派器。副栏62概述了ACE_Streams框架所支持的并发模型:
下面通过一个实例来介绍各个模块的ACE_Stream 对象来配置 display_logfile 程序,并以此作为结束。下面的程序创建一个ACE_Stream,将所有模块推入流中 :
int ACE_TMAIN (int argc, ACE_TCHAR *argv[])
{
if (argc != 2)
ACE_ERROR_RETURN ((LM_ERROR,
"usage: %s logfile\n", argv[0]),
1);
ACE_TString logfile (argv[1]);
ACE_Stream<ACE_SYNCH> stream;
//推入流中时会调用其挂钩的open函数 对任务进行初始化
//Logrec_Writer_Module Logrec_Reader_Module中open 将任务转换为主动对象
if (stream.push
(new Logrec_Writer_Module (ACE_TEXT ("Writer"))) != -1
&& stream.push
(new Logrec_Separator_Module (ACE_TEXT ("Separator"))) != -1
&& stream.push
(new Logrec_Formatter_Module (ACE_TEXT ("Formatter"))) != -1
&& stream.push
(new Logrec_Reader_Module (logfile)) != -1)
//主线程不参与处理 而是等待其他线程完成
return ACE_Thread_Manager::instance ()->wait () == 0 ? 0 : 1;
return 1;
}
在每个模块被推入流中时,ACE_Streams框架在其writer端任务上调用open()挂钩方法,对任务进行初始化。Logrec_Writer_Mocule 和 Logrec_Reader_Module 的 open()持钩方法将它们的 writer 任务转换为主动对象。主线程并不实际参与处理。在成功地将所有模块推入流中之后,它简单地等待其他线程在完成了对日志文件的处理之后,从其主动对象中退出。
main()函数在程序启动时和在处理日志文件的过程中动态地分配若干模块。我们在main()函数中忽略了大部分错误处理逻辑,以节省空间。尽管这个序不太可能在启动时把堆耗尽可是设计良好的产品应用应该对错误进行检查,并适当地处理它们。
通过 ACE_Streamms 框架来实现 display_iogfile 程序使得开发者能够透明、渐进地演化应用功能例如,要实现和配置一个Logrec Sorter 模块,其作用是改变日志记录中的各个域被显示的次序,是一件直截了当的事情。同样,ACE_Steams框架还降低了 display_logfile程序的各个模块层的复杂度。从而简化了它的实现、测试,以及维护。
最后,进行简单的总结:ACE_Streams 框架是 Fipes and Filters 模式的一种实现,它采用了面向对象设计技术、ACE TasK框架,以及 C++语言特性。ACE_Steams框架使得开发者能够更轻松地将新的或修改过的功能结合进一个 ACE_Stream 中,而不用修改不依赖于应用的框架类。例如,将一个新的服务功能层结合进一个ACE_Steam 中涉及到以下步骤:
1、从 ACE_Task 接口继承,并在 ACE_Task 子类中重新定义 open()、close()、put()和 svc()方法,以实现应用定义的功能。
2、分配一个新的 ACE_Module,其中含有一个或两个应用定义的 ACE_Task的实例,一个用于reader端,一个用于 writer 端。
3、将这个 ACE_Module 插入 ACE_Steam 中,以形成一系列有序的、按层次关联的处理能力。ACE_Streams 框架使得开发者能够创建分层的、模块化的网络化应用,并轻松地对这些应用进行扩展、调整维护和配置,此外,在ACE_Task、Service Configurator和 Streams 框架之间的协同使得开发者能够在广泛的设计和配置中进行选择,对其进行扩展和修改,从而适应无数的设计情况、run-time坏境,以及OS 平台。