简介
POSIX消息队列就是进程之间通讯的方式之一,其特点是以消息的形式交换数据(数据的交换单位是整个消息),POSIX消息队列和System V消息队列相比,有以下优点:
- 消息通知特性允许一个进程能在一条消息进入之前为空的消息队列时候,异步的通过信号或者线程的实例化接收通知
- Linux下可以通过poll()、select()、epoll监控POSIX消息队列
打开、关闭、删除消息队列
在fork()中子进程会接受其父进程的消息队列描述符的副本,并且这些描述符会引用相同的打开着的消息队列。
#include <mqueue.h>
mqd_t mq_open(const char *name,int oflag,/* mode_t mode , struct mq_attr *attr */);
//mq_open()函数创造一个新的消息队列或者打开一个已有的消息队列,返回消息队列描述符
//name参数标识消息队列
//oflag参数有以下取值O_CREATE,O_EXCL,O_RDONLY,O_RDWR,O_WRONLY(三选一),O_NONBLOCK
//如果取值包含了O_CREATE,则说明如果name消息队列不存在则会新建一个,同时还需指定mode和attr
//mode是位掩码,和文件上的掩码值是一样的,将读写权限付给相应的用户
//attr是指定新消息队列的特性,默认则为NULL
int mq_close(mqd_t mqdes);
//关闭消息队列描述符,当进程终止或者调用exec()时候,自动关闭消息队列描述符
int mq_unlink(const char *name);
//函数删除通过name标示的消息队列,并将消息队列标记为在所有进程使用完该消息队列之后销毁该队列
消息队列描述符和消息队列的关系如同文件描述符和文件的关系,如下图:
消息队列的特性
struct mq_attr {
long mq_flag; //0 or O_NONBLOCK
long mq_maxmsg; //队列中最多的消息数量
long mq_msgsize; //每个消息大小的上限
long mq_curmsgs; //当前队列中消息的数量
}
int mq_getattr(mqd_t mqdes,struct mq_attr *attr);
//获取消息队列的特性
int mq_setattr(mqd_t mqdes,const struct mq_attr *newattr,struct mq_attr *oldattr);
//修改消息队列特性
//如果oldattr不为NULL,那么则返回一个包含之前消息队列标记和消息队列特性的mq_attr
收发消息
int mq_send(mqd_t mqdes,const char *msg_ptr,size_t msg_len,unsigned int msg_prio);
//函数将msg_ptr指向的缓冲区中的消息添加到消息描述符mqdes所引用的消息队列中
//msg_len表示消息缓冲区中消息的长度,其值要小于或等于队列的mq_msgsize属性
//msg_prio表示优先级,数字越大优先级越高(若优先级相同则先入先出)
ssize_t mq_receive(mqd_t mqdes,char *msg_ptr, size_t msg_len,unsigned int *msg_prio);
//接收消息的时候,msg_len表示消息缓冲区中消息的长度,其值要大于或等于队列的mq_msgsize属性
//优先级可以设为NULL
如果消息队列已经满了(即达到mq_maxmsg限制),那么后续调用mq_send()函数会阻塞知道队列中存在可用空间或者如果标记有O_NONBLOCK则立刻返回失败。同理,如果消息队列当前在空,那么调用mq_receive()也会阻塞。
还有两个mq_timedsend()和mq_timedreceive()几乎作用于前两个一样,唯一差别在于可设置一个消息超时设置。
下面的例子在父子进程之间利用消息队列通信。
#include <mqueue.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <error.h>
int main(int argc,char *argv[])
{
mqd_t mq;
mq=mq_open("/new_mq",O_CREAT|O_EXCL|O_RDWR,0666,NULL);//mq名称须以“/”开头
if(mq<0){
printf("mq_open error\n");
exit(0);
}
int pid;
struct mq_attr attr;
if(mq_getattr(mq,&attr)==-1){
printf("mq_getattr error\n");
exit(0);
}
printf("maxmsg is %ld\n",attr.mq_maxmsg);
printf("mq_msgsize is %ld\n",attr.mq_msgsize);
printf("mq_curmsgs is %ld\n",attr.mq_curmsgs);
pid=fork();
if(pid<0){
printf("fork error\n");
exit(0);
}
else if(pid==0){//child
char buf_send[]="hello";
if(mq_send(mq,buf_send,sizeof(buf_send),1)<0){
printf("send error\n");
//printf("error information %s",strerror(errno));
exit(0);
}
mq_send(mq,buf_send,sizeof(buf_send)+1,3);
mq_send(mq,buf_send,sizeof(buf_send)+1,2);
_exit(0);
}
else{//father
sleep(2);
char *buff=malloc(attr.mq_msgsize);
for(int i=1;i<=3;i++){
if(mq_receive(mq,buff,attr.mq_msgsize,NULL)<0){
printf("mq_recieve error\n");
exit(0);
}
printf("%s\n",buff);
}
mq_close(mq);
mq_unlink("/new_mq");
}
return 0;
}
消息通知
POSIX消息队列可以让进程接受之前为空的队列上有可用消息的异步通知。一种方法通过信号接收通知,另一种通过线程接收通知。
int mq_notify(mqd_t mqdes,const struct sigevent *notification);
//mq_notify函数注册调用进程在一条消息进入描述符所引用的空队列时接收通知
使用的时候,需要注意以下几点:
- 任何一个时刻只有一个进程能够向一个指定消息队列注册接收通知。
- 只有当一条新消息进入之前为空的队列时候注册进程才会收到通知。
- 当向一个注册进程发送一个通知之后就会删除注册信息,之后任何进程就可以向该队列注册接收通知了。
参考 《TLPI》