现代的实时应用通常被构建成一组相互通信、但又相互独立的任务。这些任务可以通过若干机制来与对方进行通信,其中常用的一种就是消息队列。在这一情况下,基本的通信模式是:发送者(或生产者)任务将消息放入消息队列,而接收者(或消费者)任务从此队列中取出消息。这当然只是消息队列的使用方式之一。在接下来的讨论中,我们将看到若干不同的使用消息队列的例子。
ACE中的消息队列是仿照UNIX系统V的消息队列设计的,如果你已经熟悉系统V的话,就很容易掌握ACE的消息队列的使用。在ACE中有多种不同类型的消息队列可用,每一种都使用不同的调度算法来进行队列的入队和出队操作。
消息块 在ACE中,消息作为消息块(Message Block)被放入消息队列中。消息块包装正被存储的实际消息数据,并提供若干数据插入和处理操作。每个消息块“包含”一个头和一个数据块。注意在这里“包含”是在宽松的意义上使用的。消息块可以不对与数据块(Data Block)或是消息头(Message Header)相关联的内存进行管理(尽管你可以让消息块进行这样的管理)。它仅仅持有指向两者的指针。所以包含只是逻辑上的。数据块持有指向实际的数据缓冲区的指针。如图9-1所示,这样的设计带来了多个消息块之间的数据的灵活共享。注意在图中两个消息块共享一个数据块。这样,无需带来数据拷贝开销,就可以将同一数据放入不同的队列中。消息块类名为
ACE_Message_Block,而数据块类名为ACE_Data_Block。ACE_Message_Block的构造器是实际创建消息块和数据块的方便办法。
图
9-1 ACE消息块的构造9.1.1构造消息块ACE_Message_Block
类包含有若干不同的构造器。你可以使用这些构造器来帮助你管理隐藏在消息和数据块后面的消息数据。ACE_Message_Block类可用于完全地隐藏ACE_Data_Block,并为你管理消息数据;或者,如果你需要,你可以自己创建和管理数据块及消息数据。下一部分将考查怎样使用ACE_Message_Block来管理消息内存和数据块。然后我们将考查怎样独立地进行这样的管理,而不用依赖ACE_Message_Block的管理特性。分配和管理数据内存要创建消息块,最容易的方法是将底层数据块的大小传给
ACE_Message_Block的构造器,从而创建ACE_Data_Block,并为消息数据分配空的内存区。在创建消息块后,你可以使用rd_ptr()和wr_ptr()方法来在消息块中插入和移除数据。让ACE_Message_Block来为数据和数据块创建内存区的主要优点是,它会为你正确地管理所有内存,从而使你免于在将来为许多内存泄漏而头疼。ACE_Message_Block
的构造器还允许程序员指定ACE_Message_Block在内部分配内存时所应使用的分配器。如果你传入一个分配器,消息块将用它来为数据块和消息数据区的创建分配内存。ACE_Message_Block的构造器为:ACE_Message_Block (size_t size,
ACE_Message_Type type = MB_DATA,
ACE_Message_Block *cont = 0,
const char *data = 0,
ACE_Allocator *allocator_strategy = 0,
ACE_Lock *locking_strategy = 0,
u_long priority = 0,
const ACE_Time_Value & execution_time = ACE_Time_Value::zero,
const ACE_Time_Value & deadline_time = ACE_Time_Value::max_time);
上面的构造器的参数为:
下面的例子演示怎样将指向消息数据的指针传给消息块,以及
ACE_Message_Block怎样创建和管理底层的ACE_Data_Block。//The data
char data[size];
data = ”This is my data”;
// data that is stored in the newly created data
ACE_Message_Block *mb = new ACE_Message_Block (data,
blocksize); //size of the block that
//is to be stored.
该构造器创建底层数据块,并将它设置为指向传递给它的数据的开头。被创建的消息块并不拷贝该数据,也不假定自己拥有它的所有权。这就意味着在消息块mb被销毁时,相关联的数据缓冲区data将不会被销毁。这是有意义的:消息块没有拷贝数据,因此内存也不是它分配的,这样它也不应该负责销毁它。9.1.2在消息块中插入和操作数据
除了构造器,ACE_Message_Block还提供若干方法来直接在消息块中插入数据。另外还有一些方法可用来操作已经在消息块中的数据。
每个ACE_Message_Block都有两个底层指针:rd_ptr和wr_ptr,用于在消息块中读写数据。它们可以通过调用rd_ptr()和wr_ptr()方法来直接访问。rd_ptr指向下一次读取数据的位置,而wr_ptr指向下一次写入数据的位置。程序员必须小心地管理这些指针,以保证它们总是指向正确的位置。在使用这些指针读写数据时,程序员必须自己来增加它们的值,它们不会魔法般地自动更新。大多数内部的消息块方法也使用这两个指针,从而使它们能够在你调用消息块方法时改变指针状态。程序员必须保证自己了解这些指针的变化。
9.1.2.1
拷贝与复制(Copying and Duplicating)可以使用
ACE_Message_Block的copy()方法来将数据拷贝进消息块。int copy(const char *buf, size_t n);
copy
方法需要两个参数,其一是指向要拷贝进消息块的缓冲区的指针,其二是要拷贝的数据的大小。该方法从wr_pt指向的位置开始往前写,直到它到达参数n所指示的数据缓冲区的末尾。copy()还会保证wr_ptr的更新,使其指向缓冲区的新末尾处。注意该方法将实际地执行物理拷贝,因而应该小心使用。base()
和length()方法可以联合使用,以将消息块中的整个数据缓冲区拷贝出来。base()返回指向数据块的第一个数据条目的指针,而length()返回队中数据的总大小。将base和length相加,可以得到指向数据块末尾的指针。合起来使用这些方法,你就可以写一个例程来从消息块中取得数据,并做一次外部拷贝。duplicate()
和clone()方法用于制作消息块的“副本”。如它的名字所暗示的,clone()方法实际地创建整个消息块的新副本,包括它的数据块和附加部分;也就是说,这是一次“深度复制”。而另一方面,duplicate()方法使用的是ACE_Message_Block的引用计数机制。它返回指向要被复制的消息块的指针,并在内部增加内部引用计数。9.1.2.2释放消息块一旦使用完消息块,程序员可以调用它的
release()方法来释放它。如果消息数据内存是由该消息块分配的,调用release()方法就也会释放此内存。如果消息块是引用计数的,release()就会减少计数,直到到达0为止;之后消息块和与它相关联的数据块才从内存中被移除。的消息队列 如先前所提到的,ACE有若干不同类型的消息队列,它们大体上可划分为两种范畴:静态的和动态的。静态队列是一种通用的消息队列(ACE_Message_Queue),而动态消息队列(ACE_Dynamic_Message_Queue)是实时消息队列。这两种消息队列的主要区别是:静态队列中的消息具有静态的优先级,也就是,一旦优先级被设定就不会再改变;而另一方面,在动态消息队列中,基于诸如执行时间和最终期限等参数,消息的优先级可以动态地改变。下面的例子演示怎样创建简单的静态消息队列,以及怎样在其上进行消息块的入队和出队操作。
