消息队列可认为是一个消息链表,它允许进程之间以消息的形式交换数据。有足够写权限的进程或线程可往队列中放置消息,有足够读权限的进程或线程可从队列中取走消息。每个消息都是一个记录,它由发送者赋予一个优先级。与管道不同,管道是字节流模型,没有消息边界。
本文介绍的是POSIX消息队列。POSIX消息队列与System V消息队列的相似之处在于数据的交换单位是整个消息,但它们之间仍然存在一些显著的差异。
POSIX消息队列API中的主要函数如下。
上面的函数所完成的功能是相当明显的。此外,POSIX消息队列API还具备一些特别的特性。
mq_open()函数创建一个新消息队列或打开一个既有队列。
#include
#include
#include
mqd_t mq_open(const char *name,int oflag,mode_t mode,struct mq_attr *attr);
name参数标识出了消息队列,消息队列对象的名字的最大长度为NAME_MAX(255)个字符。
oflag参数是一个位掩码,它控制着mq_open()操作的各个方面。
oflag参数的位值:
标记 | 描述 |
---|---|
O_CREAT | 队列不存在时创建队列 |
O_EXCL | 与O_CREAT一起使用,若消息队列已存在,则错误返回 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
O_NONBLOCK | 以非阻塞模式打开 |
oflag参数的其中一个用途是,确定是打开一个既有队列还是创建和打开一个新队列。如果在oflag中不包含O_CREAT,那么将会打开一个既有队列。如果在oflag中包含了O_CREAT,并且与给定的name对应的队列不存在,那么就会创建一个新的空队列。
如果在oflag中同时包含O_CREAT和O_EXCL,并且与给定的name对应的队列已经存在,那么mq_open()就会失败。
mq_open()通常用来打开一个既有消息队列,这种调用只需要两个参数,但如果在flags中指定了O_CREAT,那么就还需要另外两个参数:mode和attr。这些参数用法如下:
mq_open()在成功结束时会返回一个消息队列描述符,它是一个类型为mqd_t的值,在后续的调用中将会使用它来引用这个打开着的消息队列。
mq_close()函数关闭消息队列描述符mqdes。
#include
int mq_close(mqd_t mqdes);
与文件上的close()一样,关闭一个消息队列并不会删除该队列。要删除队列则需要使用mq_unlinl(),它是unlink()在消息队列上的版本。
mq_unlink()函数删除通过name标识的消息队列,并将队列标记为在所有进程使用完该队列之后销毁该队列。
#include
int mq_unlink(const char *name);
mq_open()、mq_getattr()以及mq_setattr()函数都会接收一个参数,它是一个指向mq_attr结构的指针。这个结构是在
struct mq_attr
{
long mq_flags;//阻塞标志, 0或O_NONBLOCK
long mq_maxmsg;//最大消息数
long mq_msgsize;//每个消息最大大小
long mq_curmsgs;//当前消息数
};
在开始深入介绍mq_attr的细节之前有必要指出以下几点。
在使用mq_open()创建消息队列时可以通过下列mq_attr字段来确定队列的特性。
mq_maxmsg和mq_msgsize特性是在消息队列被创建时就确定下来的,并且之后也无法修改这两个特性。
mq_getattr()函数返回一个包含与描述符mqdes相关联的消息队列描述和消息队列的相关信息的mq_attr结构。
#include
int mq_getattr(mqd_t mqdes,struct mq_attr *attr);
除了上面已经介绍的mq_maxmsg和mq_msgsize字段之外,attr指向的返回结构中还包含下列字段。
mq_flags
这些是与描述符mqdes相关联的打开的消息队列描述的标记,其取值只有一个:O_NONBLOCK。这个标记是根据mq_open()的oflag参数来初始化的,并且使用mq_setattr()可以修改这个标记。
mq_curmsgs
当前位于队列中的消息数。这个信息在mq_getattr()返回时可能已经发生了改变,前提是存在其他进程从队列中读取消息或写入消息。
mq_setattr()函数设置与消息队列描述符mqdes相关联的消息队列描述的特性,并可选地返回与消息队列有关的消息。
#include
int mq_setattr(mq_t mqdes,const struct mq_attr *newattr,struct mq_attr *oldattr);
mq_setattr()函数执行下列任务。
mq_send()函数将位于msg_ptr指向的缓冲区中的消息添加到描述符mqdes所引用的消息队列中。
#include
int mq_send(mqd_t mqdes,const char *msg_ptr,size_t msg_len,unsigned int msg_prio);
msg_len参数指定了msg_ptr指向的消息的长度,其值必须小于或等于队列的mq_msgsize特性,否则mq_send()就会返回EMSGSIZE错误。长度为零的消息是允许的。
每条消息都拥有一个用非负整数表示的优先级,它通过msg_prio参数指定。消息在队列中是按照优先级倒序排列的(即0表示优先级最低)。当一条消息被添加到队列中时,它会被放置在队列中具有相同优先级的所有消息之后。如果一个应用程序无需使用消息优先级,那么只需要将msg_prio指定为0即可。
mq_receive()函数从mqdes引用的消息队列中删除一条优先级最高、存在时间最长的消息并将删除的消息放置在msg_ptr指向的缓冲区。
#include
ssize_t mq_receive(mqd_t mqdes, char *mdg_ptr,size_t msg_len,unsigned int *msg_prio);
调用着使用msg_len参数来指定msg_ptr指向的缓冲区中的可用字节数。
不管消息的实际大小是什么,msg_len必须要大于或等于队列的mq_msgsize特性,否则mq_receive()就会失败并返回EMSGSIZE错误。如果不清楚一个队列的mq_msgsize特性的值,那么可以使用mq_getattr()来获取这个值。
如果msg_prio不为NULL,那么接收到的消息的优先级会被复制到msg_prio指向的位置处。
POSIX消息队列能够接收之前为空的队列上有可用消息的异步通知(即队列从空变成了非空)。这个特性意味着已经无需执行一个阻塞的调用或将消息队列描述符标记为非阻塞并在队列上定期执行mq_receive()调用。
进程可以选择通过信号的形式或者通过一个单独的线程中调用一个函数的形式来接收通知。
mq_notify()函数注册调用进程在一条消息进入描述符mqdes引用的空队列时接收通知。
#include
int mq_notify(mqd_t mqdes,const struct sigevent *notification);
notification参数指定了进程接收通知的机制。
有关消息的通知需要注意以下几点:
struct sigevent
{
int sigev_notify;
int sigev_signo;
union sigval sigev_value;
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
};
sigev_notify取值:
SIGEV_NONE:事件发生时,什么也不做;
SIGEV_SIGNAL:事件发生时,将sigev_signo指定的信号发送给指定的进程;SIGEV_THREAD:事件发生时,内核会(在此进程内)以sigev_notify_attributes为线程属性创建一个线程,并让其执行sigev_notify_function,并以sigev_value为其参数
sigev_signo:在sigev_notify=SIGEV_SIGNAL时使用,指定信号类别
sigev_value:sigev_notify=SIGEV_SIGEV_THREAD时使用,作为sigev_notify_function的参数
union sigval
{
int sival_int;
void *sival_ptr;
};
sigev_notify_function:在sigev_notify=SIGEV_THREAD时使用,其他情况下置NULL
sigev_notify_attributes:在sigev_notify=SIGEV_THREAD时使用,指定创建线程的属性,其他情况下置NULL