1. 前言
boost中的消息队列(Message Queue)是进程间通信的一种机制,实际上是其内部也是采用共享内存的方式来达到进程间通信的目的。这也就意味这Message Queue有其局限性:只有处在同一台计算机中的不同进程才能使用消息队列进行通信。消息队列类似于消息列表,每个线程能够往列表里塞消息,也能从列表里读取消息。每一条消息都有三个属性:
1.消息优先级
2.消息长度
3.消息内容
2. 消息队列的使用
1. 消息队列的构造函数
消息队列的构造函数有三个分别是:
message_queue_t(create_only_t create_only,
const char *name,
size_type max_num_msg,
size_type max_msg_size,
const permissions &perm = permissions());
该构造函数的作用是创建一个消息队列,name用于指定消息队列的名字,max_num_msg指定创建的消息队列最多能有多少条消息同时存在,max_msg_size表示了所有的消息中最大的消息长度,最后一个参数指定了消息队列的权限,默认是0644。值得注意的是,如果已经有一个同名的消息队列存在,那么该构造函数就会抛异常。
message_queue_t(open_or_create_t open_or_create,
const char *name,
size_type max_num_msg,
size_type max_msg_size,
const permissions &perm = permissions());
第二个构造函数和的第一个构造函数大同小异,和第一个构造函数的区别是如果已经存在了一个同名消息队列就打开该消息队列,否则则创建新的消息队列。
message_queue_t(open_only_t open_only,
const char *name);
第三个构造函数仅仅是打开一个消息队列,如果没有该消息队列,则会抛出异常。
2. 发送消息
在消息队列中,不管是发送消息还是接收消息都有三种模式,也分别对应了三个发送或接收的方法。
1:Blocking:阻塞,对于发送方来说,如果消息队列满了,则发送消息会被阻塞,直到消息队列有多余的位置来存放此条消息。而对于接收方接收数据时,如果消息队列为空,也会阻塞直到消息队列中有数据可读。
2:Try,非阻塞的方式,不管消息队列是空还是满都会立刻返回false。
3:Timed,阻塞的方式,和第一种不同的时,他会阻塞一定的时间,如果时间到了队列还是空或满的,则会返回false。
对于发送方,其三种发送的方式如下:
void send(const void *buffer, size_type buffer_size,
unsigned int priority);
bool try_send(const void *buffer, size_type buffer_size,
unsigned int priority);
bool timed_send(const void *buffer, size_type buffer_size,
unsigned int priority, const boost::posix_time::ptime& abs_time);
如上,send是阻塞的方法,try_send是非阻塞的方法,timed_send是有时间限制阻塞的方法。
参数buffer表示是消息的缓冲区,buffer_size表示buffer的大小,priority表示消息的优先级,abs_time则表示等待的时间。
但其实不管是哪种方法,都不过是对do_send方法的一层封装。
template<class VoidPointer>
inline void message_queue_t<VoidPointer>::send
(const void *buffer, size_type buffer_size, unsigned int priority)
{ this->do_send(blocking, buffer, buffer_size, priority, ptime()); }
template<class VoidPointer>
inline bool message_queue_t<VoidPointer>::try_send
(const void *buffer, size_type buffer_size, unsigned int priority)
{ return this->do_send(non_blocking, buffer, buffer_size, priority, ptime()); }
template<class VoidPointer>
inline bool message_queue_t<VoidPointer>::timed_send
(const void *buffer, size_type buffer_size
,unsigned int priority, const boost::posix_time::ptime &abs_time)
{
if(abs_time == boost::posix_time::pos_infin){
this->send(buffer, buffer_size, priority);
return true;
}
return this->do_send(timed, buffer, buffer_size, priority, abs_time);
}
do_send方法的第一个参数就是所使用的模式,阻塞,非阻塞还有有时间限制的阻塞。
简单看一下do_send内部的一些处理:
do_send方法第一步的处理就是对buffer_size和max_msg_size进行比较,如果buffer_size大于max_msg_size就直接抛异常,如下:
if (buffer_size > p_hdr->m_max_msg_size) {
throw interprocess_exception(size_error);
}
第二步就是对当消息队列满时的处理,处理的策略就是根据传进来的第一参数来决定的,所以阻塞,非阻塞还是有时间限制的阻塞,其具体的实现就在下面的代码块中。
if (p_hdr->is_full()) {
BOOST_TRY{
#ifdef BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX
++p_hdr->m_blocked_senders;
#endif
switch(block){
case non_blocking :
#ifdef BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX
--p_hdr->m_blocked_senders;
#endif
return false;
break;
case blocking :
do{
p_hdr->m_cond_send.wait(lock);
}
while (p_hdr->is_full());
break;
case timed :
do{
if(!p_hdr->m_cond_send.timed_wait(lock, abs_time)){
if(p_hdr->is_full()){
#ifdef BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX
--p_hdr->m_blocked_senders;
#endif
return false;
}
break;
}
}
while (p_hdr->is_full());
break;
default:
break;
}
#ifdef BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX
--p_hdr->m_blocked_senders;
#endif
}
BOOST_CATCH(...){
#ifdef BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX
--p_hdr->m_blocked_senders;
#endif
BOOST_RETHROW;
}
BOOST_CATCH_END
}
第三步就是拷贝内存,其处理就是通过memcpy函数将buffer的数据拷贝到消息队列中。
free_msg_hdr.priority = priority;
free_msg_hdr.len = buffer_size;
//Copy user buffer to the message
std::memcpy(free_msg_hdr.data(), buffer, buffer_size);
3. 接收消息
接收消息同样也有三种方法,阻塞,非阻塞和有时间限制的阻塞三种。方法如下:
inline void message_queue_t<VoidPointer>::receive(void *buffer, size_type buffer_size,
size_type &recvd_size, unsigned int &priority)
{ this->do_receive(blocking, buffer, buffer_size, recvd_size, priority, ptime()); }
template<class VoidPointer>
inline bool
message_queue_t<VoidPointer>::try_receive(void *buffer, size_type buffer_size,
size_type &recvd_size, unsigned int &priority)
{ return this->do_receive(non_blocking, buffer, buffer_size, recvd_size, priority, ptime()); }
template<class VoidPointer>
inline bool
message_queue_t<VoidPointer>::timed_receive(void *buffer, size_type buffer_size,
size_type &recvd_size, unsigned int &priority,
const boost::posix_time::ptime &abs_time)
{
if(abs_time == boost::posix_time::pos_infin){
this->receive(buffer, buffer_size, recvd_size, priority);
return true;
}
return this->do_receive(timed, buffer, buffer_size, recvd_size, priority, abs_time);
}
与发送方法类似,这三种方法也不过是对do_receive方法的一层封装。
do_receive函数原型如下:
template<class VoidPointer>
inline bool
message_queue_t<VoidPointer>::do_receive(block_t block,
void *buffer, size_type buffer_size,
size_type &recvd_size, unsigned int &priority,
const boost::posix_time::ptime &abs_time)
第一个参数block则是表示三种模式之一,buffer表示等待接收数据的缓冲区,buffer_size表示缓冲区的大小,recvd_size表示实际接收到的字节数,priority表示消息优先级,abs_time表示的是阻塞的最大时间。
do_receive的处理逻辑和do_send的处理逻辑大同小异。第一步也是对buffer_size与max_msg_size进行比较,如果buffer_size小于max_msg_size则会抛异常,这是为了保证接收方的buffer要比消息队列中最大消息长度都要大,以满足消息的正常接收。
if (buffer_size < p_hdr->m_max_msg_size) {
throw interprocess_exception(size_error);
}
第二步是对消息队列为空时的处理,和发送方对消息队列为满时的处理一致,这里不再赘述。
第三步拷贝数据也是简单的调用memcpy拷贝内存数据。
recvd_size = top_msg.len;
priority = top_msg.priority;
//Some cleanup to ease debugging
top_msg.len = 0;
top_msg.priority = 0;
//Copy data to receiver's bufers
std::memcpy(buffer, top_msg.data(), recvd_size);
//Free top message and put it in the free message list
p_hdr->free_top_msg();
4.生产者消费者demo
了解了消息队列的发送和接收方法之后,来通过一个简单的生产-消费者模型演示其用法。
生产者:
#include <boost/interprocess/ipc/message_queue.h>
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char buff[20];
message_queue mq(create_only, "mqtest", 10, 20);
memset(buff,0,20);
*(int*)buff = 10;
mq.send(buff,20,0);
memset(buff,0,20);
*(int*)buff = 11;
*(double*)&buff[sizeof(int)] = 6.6;
mq.send(buff,20,0);
return 0;
}
编译生产者:
g++ send.cpp -o send -lboost_thread -lpthread -lrt
上述生产者向消息队列发送了两次消息,一次是10,一次是11和6.6。
消费者:
#include <boost/interprocess/ipc/message_queue.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
int main()
{
char buff[20];
try
{
message_queue mq(open_only, "mqtest");
unsigned int priority = 0;
unsigned long size = 0;
mq.receive(buff,20,size,priority);
printf("int = %d,size = %d\n",*(int*)buff,size);
size = 0;
mq.revice(buff,20,size,priority);
printf("int = %d,double = %f,size = %d\n", *(int*)buff,*(double*)&buff[sizeof(int)],size);
}
catch(interprocess_exception &ex)
{
cout << ex.what() <<endl;
mq.remove("mqtest");
}
return 0;
}
编译消费者:
g++ receive.cpp -o receive -lboost_thread -lpthread -lrt
消费者则是从消息队列中读取了两条消息并且打印出来看是否是生产者写进去的消息。
5. 消息队列的其他方法
除了发送和接收,消息队列的类中还有一些其他方法,如下:
获取消息队列最大能容纳的消息数:
size_type get_max_msg() const;
获取消息队列所能容纳消息的最大长度:
size_type get_max_msg_size() const;
获取当前消息队列中的消息数目:
size_type get_num_msg() const;
移除一个消息队列:
static bool remove(const char *name);
6.消息队列的安全性
如果同一个消息队列却有好几个生产者要发送消息给消息队列,又或者有好几个消费者要从消息队列中读取数据怎么办?是否线程安全?这个其实不必担心,在do_receive或者do_send方法中都有加互斥锁的处理,不仅仅是生产者与生产者之间互斥,消费者与消费者互斥,事实上生产者与消费者之间也是互斥的。例如,一个消息队列有一个生产者,一个消费者,当生产者往消息队列里写数据时,消费者是不能够读取数据的,同理,消息者在读数据时,生产者也无法写数据。
7.消息队列的使用注意事项
在使用消息队列时,有几个点是需要注意的:
1.不管在生产者中还是消费者中都要有捕获异常的处理,当捕捉到异常后需要调用remove方法,避免下次执行会失败。
2.生产者写数据个消息队列时,其大小不能超过消息队列所能容纳的最大消息长度,消费者在读数据时,其缓冲区大小不能小于消息队列的最大消息长度,否则都会抛异常:library_error。谨记。