Linux进程间通信(IPC)编程实践(十二)Posix消息队列--基本API的使用

posix消息队列与system v消息队列的差别:

(1)对posix消息队列的读总是返回最高优先级的最早消息,对system v消息队列的读则可以返回任意指定优先级的消息。
(2)当往一个空队列放置一个消息时,posix消息队列允许产生一个信号或启动一个线程,system v消息队列则不提供类似机制。


队列中的每个消息具有如下属性:

1、一个无符号整数优先级(posix)或一个长整数类型(system v)
2、消息的数据部分长度(可以为0)
3、数据本身(如果长度大于0)

Posix消息队列操作函数如下:

1. 创建/获取一个消息队列

[cpp]  view plain  copy
  1. mqd_t mq_open(const char *name, int oflag); //专用于打开一个消息队列    
  2. mqd_t mq_open(const char *name, int oflag, mode_t mode,    
  3.               struct mq_attr *attr);  

参数:

   name:  消息队列名字;

   oflag: 与open函数类型, 可以是O_RDONLY, O_WRONLY, O_RDWR, 还可以按位或上O_CREAT, O_EXCL, O_NONBLOCK.

   mode: 如果oflag指定了O_CREAT, 需要指定mode参数;

   attr: 指定消息队列的属性;

返回值:

   成功: 返回消息队列文件描述符;

   失败: 返回-1;

注意-Posix IPC名字限制:

   1. 必须以”/”开头, 并且后面不能还有”/”, 形如:/file-name;

   2. 名字长度不能超过NAME_MAX

   3. 链接时:Link with -lrt(Makefile中使用实时链接库-lrt)

2. 关闭一个消息队列

[cpp]  view plain  copy
  1. #include    <mqueue.h>  
  2. int mq_close(mqd_t mqdes);  

返回: 成功时为0,出错时为-1。
功能: 关闭已打开的消息队列。

注意:System V没有此功能函数调用

3. 删除一个消息队列

[cpp]  view plain  copy
  1. int mq_unlink(const char *name);    
  2. /** System V 消息队列  
  3. 通过msgctl函数, 并将cmd指定为IPC_RMID来实现  
  4. int msgctl(int msqid, int cmd, struct msqid_ds *buf);  
  5. **/   
返回: 成功时为0,出错时为-1
功能: 从系统中删除消息队列。

对上述三个函数的综合使用:
[cpp]  view plain  copy
  1. int main()    
  2. {    
  3.     mqd_t mqid = mq_open("/abc", O_CREAT|O_RDONLY, 0666, NULL);    
  4.     if (mqid == -1)    
  5.         err_exit("mq_open error");    
  6.     cout << "mq_open success" << endl;    
  7.     mq_close(mqid);    
  8.     mq_unlink("/abc");    
  9.     cout << "unlink success" << endl;    
  10. }  
4. 获取/设置消息队列属性
[cpp]  view plain  copy
  1. #include    <mqueue.h>  
  2. int mq_getattr(mqd_t mqdes, struct mq_attr *attr);  
  3. int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *attr);  
均返回:成功时为0, 出错时为-1

参数:

   newattr: 需要设置的属性

   oldattr: 原来的属性


每个消息队列有四个属性:
[cpp]  view plain  copy
  1. struct mq_attr  
  2. {  
  3.     long mq_flags;      /* message queue flag : 0, O_NONBLOCK */  
  4.     long mq_maxmsg;     /* max number of messages allowed on queue*/  
  5.     long mq_msgsize;    /* max size of a message (in bytes)*/  
  6.     long mq_curmsgs;    /* number of messages currently on queue */  
  7. };  

[cpp]  view plain  copy
  1. int main(int argc,char **argv)    
  2. {    
  3.     mqd_t mqid = mq_open("/test", O_RDONLY|O_CREAT, 0666, NULL);    
  4.     if (mqid == -1)    
  5.         err_exit("mq_open error");    
  6.     
  7.     struct mq_attr attr;    
  8.     if (mq_getattr(mqid, &attr) == -1)    
  9.         err_exit("mq_getattr error");    
  10.     cout << "Max messages on queue: " << attr.mq_maxmsg << endl;    
  11.     cout << "Max message size: " << attr.mq_msgsize << endl;    
  12.     cout << "current messages: " << attr.mq_curmsgs << endl;    
  13.     
  14.     mq_close(mqid);    
  15.     return 0;    
  16. }    
对比System V:

通过msgctl函数, 并将cmd指定为IPC_STAT/IPC_SET来实现

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

另外每个消息均有一个优先级,它是一个小于MQ_PRIO_MAX的无符号整数
#define MQ_PRIO_MAX 32768


5. 发送消息/读取消息

[cpp]  view plain  copy
  1. #include    <mqueue.h>  
  2. int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);  
  3.   
  4. ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *priop);  

返回:成功时为0,出错为-1

返回:成功时为消息中的字节数,出错为-1
参数:  最后一个是消息的优先级

消息队列的限制:
MQ_OPEN_MAX : 一个进程能够同时拥有的打开着消息队列的最大数目
MQ_PRIO_MAX : 任意消息的最大优先级值加1

[cpp]  view plain  copy
  1. /** 示例: 向消息队列中发送消息, prio需要从命令行参数中读取 **/    
  2. struct Student    
  3. {    
  4.     char name[36];    
  5.     int age;    
  6. };    
  7. int main(int argc,char **argv)    
  8. {    
  9.     if (argc != 2)    
  10.         err_quit("./send <prio>");    
  11.     
  12.     mqd_t mqid = mq_open("/test", O_WRONLY|O_CREAT, 0666, NULL);    
  13.     if (mqid == -1)    
  14.         err_exit("mq_open error");    
  15.     
  16.     struct Student stu = {"xiaofang", 23};    
  17.     unsigned prio = atoi(argv[1]);    
  18.     if (mq_send(mqid, (const char *)&stu, sizeof(stu), prio) == -1)    
  19.         err_exit("mq_send error");    
  20.     
  21.     mq_close(mqid);    
  22.     return 0;    
  23. }    

[cpp]  view plain  copy
  1. /** 示例: 从消息队列中获取消息 **/    
  2. int main(int argc,char **argv)    
  3. {    
  4.     mqd_t mqid = mq_open("/test", O_RDONLY);    
  5.     if (mqid == -1)    
  6.         err_exit("mq_open error");    
  7.     
  8.     struct Student buf;    
  9.     int nrcv;    
  10.     unsigned prio;    
  11.     struct mq_attr attr;    
  12.     if (mq_getattr(mqid, &attr) == -1)    
  13.         err_exit("mq_getattr error");    
  14.     
  15.     if ((nrcv = mq_receive(mqid, (char *)&buf, attr.mq_msgsize, &prio)) == -1)    
  16.         err_exit("mq_receive error");    
  17.     
  18.     cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "    
  19.          << buf.name << ", age: " << buf.age << endl;    
  20.     
  21.     mq_close(mqid);    
  22.     return 0;    
  23. }    
 6.建立/删除消息到达通知事件
[cpp]  view plain  copy
  1. #include    <mqueue.h>  
  2. int mq_notify(mqd_t mqdes, const struct sigevent *notification);  
返回: 成功时为0,出错时为-1
功能: 给指定队列建立或删除异步事件通知

sigev_notify代表通知的方式: 一般常用两种取值:SIGEV_SIGNAL, 以信号方式通知; SIGEV_THREAD, 以线程方式通知

如果以信号方式通知: 则需要设定一下两个参数:

   sigev_signo: 信号的代码

   sigev_value: 信号的附加数据(实时信号)

如果以线程方式通知: 则需要设定以下两个参数:

   sigev_notify_function

   sigev_notify_attributes

[cpp]  view plain  copy
  1. union sigval  
  2. {  
  3.     int sival_int;      /* Integer value */  
  4.     void *sival_ptr;    /* pointer value */  
  5. };  
  6.   
  7. struct sigevent  
  8. {  
  9.     int     sigev_notify;   /* SIGEV_{ NONE, ISGNAL, THREAD} */  
  10.     int     sigev_signo;    /* signal number if SIGEV_SIGNAL */  
  11.     union sigval sigev_value;   /* passed to signal handler or thread */  
  12.     void    (*sigev_notify_function)(union sigval);  
  13.     pthread_attr_t *sigev_notify_attribute;  
  14. };   

参数sevp:

   NULL: 表示撤销已注册通知;

   非空: 表示当消息到达且消息队列当前为空, 那么将得到通知;

通知方式:

   1. 产生一个信号, 需要自己绑定

   2. 创建一个线程, 执行指定的函数

注意: 这种注册的方式只是在消息队列从空到非空时才产生消息通知事件, 而且这种注册方式是一次性的!

** Posix IPC所特有的功能, System V没有 **/

[cpp]  view plain  copy
  1. /**示例: 将下面程序多运行几遍, 尤其是当消息队列”从空->非空”, 多次”从空->非空”, 当消息队列不空时运行该程序时, 观察该程序的状态;  
  2. **/    
  3. mqd_t mqid;    
  4. long size;    
  5. void sigHandlerForUSR1(int signo)    
  6. {    
  7.     //将数据的读取转移到对信号SIGUSR1的响应函数中来    
  8.     struct Student buf;    
  9.     int nrcv;    
  10.     unsigned prio;    
  11.     if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)    
  12.         err_exit("mq_receive error");    
  13.     
  14.     cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "    
  15.          << buf.name << ", age: " << buf.age << endl;    
  16. }    
  17.     
  18. int main(int argc,char **argv)    
  19. {    
  20.     // 安装信号响应函数    
  21.     if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)    
  22.         err_exit("signal error");    
  23.     
  24.     mqid = mq_open("/test", O_RDONLY);    
  25.     if (mqid == -1)    
  26.         err_exit("mq_open error");    
  27.     
  28.     // 获取消息的最大长度    
  29.     struct mq_attr attr;    
  30.     if (mq_getattr(mqid, &attr) == -1)    
  31.         err_exit("mq_getattr error");    
  32.     size = attr.mq_msgsize;    
  33.     
  34.     // 注册消息到达通知事件    
  35.     struct sigevent event;    
  36.     event.sigev_notify = SIGEV_SIGNAL;  //指定以信号方式通知    
  37.     event.sigev_signo = SIGUSR1;        //指定以SIGUSR1通知    
  38.     if (mq_notify(mqid, &event) == -1)    
  39.         err_exit("mq_notify error");    
  40.     
  41.     //死循环, 等待信号到来    
  42.     while (true)    
  43.         pause();    
  44.     
  45.     mq_close(mqid);    
  46.     return 0;    
  47. }    

[cpp]  view plain  copy
  1. /** 示例:多次注册notify, 这样就能过多次接收消息, 但是还是不能从队列非空的时候进行接收, 将程序改造如下:  
  2. **/    
  3. mqd_t mqid;    
  4. long size;    
  5. struct sigevent event;    
  6. void sigHandlerForUSR1(int signo)    
  7. {    
  8.     // 注意: 是在消息被读走之前进行注册,    
  9.     // 不然该程序就感应不到消息队列"从空->非空"的一个过程变化了    
  10.     if (mq_notify(mqid, &event) == -1)    
  11.         err_exit("mq_notify error");    
  12.     
  13.     //将数据的读取转移到对信号SIGUSR1的响应函数中来    
  14.     struct Student buf;    
  15.     int nrcv;    
  16.     unsigned prio;    
  17.     if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)    
  18.         err_exit("mq_receive error");    
  19.     
  20.     cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "    
  21.          << buf.name << ", age: " << buf.age << endl;    
  22. }    
  23.     
  24. int main(int argc,char **argv)    
  25. {    
  26.     // 安装信号响应函数    
  27.     if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)    
  28.         err_exit("signal error");    
  29.     
  30.     mqid = mq_open("/test", O_RDONLY);    
  31.     if (mqid == -1)    
  32.         err_exit("mq_open error");    
  33.     
  34.     // 获取消息的最大长度    
  35.     struct mq_attr attr;    
  36.     if (mq_getattr(mqid, &attr) == -1)    
  37.         err_exit("mq_getattr error");    
  38.     size = attr.mq_msgsize;    
  39.     
  40.     // 注册消息到达通知事件    
  41.     event.sigev_notify = SIGEV_SIGNAL;  //指定以信号方式通知    
  42.     event.sigev_signo = SIGUSR1;        //指定以SIGUSR1通知    
  43.     if (mq_notify(mqid, &event) == -1)    
  44.         err_exit("mq_notify error");    
  45.     
  46.     //死循环, 等待信号到来    
  47.     while (true)    
  48.         pause();    
  49.     
  50.     mq_close(mqid);    
  51.     return 0;    
  52. }    

mq_notify 注意点总结:

   1. 任何时刻只能有一个进程可以被注册为接收某个给定队列的通知;

   2. 当有一个消息到达某个先前为空的队列, 而且已有一个进程被注册为接收该队列的通知时, 只有没有任何线程阻塞在该队列的mq_receive调用的前提下, 通知才会发出;

   3. 当通知被发送给它的注册进程时, 该进程的注册被撤销. 进程必须再次调用mq_notify以重新注册(如果需要的话),但是要注意: 重新注册要放在从消息队列读出消息之前而不是之后(如同示例程序);


异步信号安全函数
[cpp]  view plain  copy
  1. #include    <signal.h>  
  2. int sigwait(const sigset_t *set, int *sig);  

可以使用sigwait函数代替信号处理程序的信号通知,将信号阻塞到某个函数中,仅仅等待该信号的递交。采用sigwait实现上面的程序如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <mqueue.h>  
  5. #include <fcntl.h>  
  6. #include <errno.h>  
  7. #include <signal.h>  
  8.   
  9. int main(int argc,char *argv[])  
  10. {  
  11.     mqd_t       mqd;  
  12.     int         signo;  
  13.     void        *buff;  
  14.     ssize_t     n;  
  15.     sigset_t    newmask;  
  16.     struct mq_attr  attr;  
  17.     struct sigevent sigev;  
  18.     if(argc != 2)  
  19.     {  
  20.         printf("usage :mqnotify <name>");  
  21.         exit(0);  
  22.     }  
  23.     mqd = mq_open(argv[1],O_RDONLY);  
  24.     mq_getattr(mqd,&attr);  
  25.     buff = malloc(attr.mq_msgsize);  
  26.     sigemptyset(&newmask);  
  27.     sigaddset(&newmask,SIGUSR1);  
  28.     sigprocmask(SIG_BLOCK,&newmask,NULL);  
  29.       
  30.     sigev.sigev_notify = SIGEV_SIGNAL;  
  31.     sigev.sigev_signo = SIGUSR1;  
  32.     if(mq_notify(mqd,&sigev) == -1)  
  33.     {  
  34.         perror("mq_notify error");  
  35.         exit(-1);  
  36.     }  
  37.     for(; ;)  
  38.     {  
  39.        sigwait(&newmask,&signo); //阻塞并等待该信号  
  40.        if(signo == SIGUSR1)  
  41.        {  
  42.             mq_notify(mqd,&sigev);  
  43.             while((n = mq_receive(mqd,buff,attr.mq_msgsize,NULL))>=0)  
  44.                 printf("read %ld bytes\n",(long) n);  
  45.             if(errno != EAGAIN)  
  46.             {  
  47.                 perror("mq_receive error");  
  48.                 exit(-1);  
  49.             }  
  50.        }  
  51.     }  
  52.     eixt(0);  
  53. }  

 启动线程处理消息通知,程序如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <mqueue.h>  
  5. #include <fcntl.h>  
  6. #include <errno.h>  
  7. #include <signal.h>  
  8.   
  9. mqd_t       mqd;  
  10. struct mq_attr  attr;  
  11. struct sigevent sigev;  
  12. static void notify_thread(union sigval);  
  13.   
  14. int main(int argc,char *argv[])  
  15. {  
  16.   
  17.     if(argc != 2)  
  18.     {  
  19.         printf("usage :mqnotify <name>");  
  20.         exit(0);  
  21.     }  
  22.     mqd = mq_open(argv[1],O_RDONLY | O_NONBLOCK);  
  23.     mq_getattr(mqd,&attr);  
  24.   
  25.     sigev.sigev_notify = SIGEV_THREAD;  
  26.     sigev.sigev_value.sival_ptr = NULL;  
  27.     sigev.sigev_notify_function = notify_thread;  
  28.     sigev.sigev_notify_attributes = NULL;  
  29.   
  30.     if(mq_notify(mqd,&sigev) == -1)  
  31.     {  
  32.         perror("mq_notify error");  
  33.         exit(-1);  
  34.     }  
  35.     for(; ;)  
  36.     {  
  37.         pause();  
  38.     }  
  39.     eixt(0);  
  40. }  
  41. static void notify_thread(union sigval arg)  
  42. {  
  43.     ssize_t     n;  
  44.     void        *buff;  
  45.     printf("notify_thread started\n");  
  46.     buff = malloc(attr.mq_msgsize);  
  47.     mq_notify(mqd,&sigev);  
  48.     while((n = mq_receive(mqd,buff,attr.mq_msgsize,NULL))>=0)  
  49.                 printf("read %ld bytes\n",(long) n);  
  50.     if(errno != EAGAIN)  
  51.     {  
  52.                 perror("mq_receive error");  
  53.                 exit(-1);  
  54.     }  
  55.     free(buff);  
  56.     pthread_exit(NULL);  
  57. }  

附-查看已经成功创建的Posix消息队列

#其存在与一个虚拟文件系统中, 需要将其挂载到系统中才能查看

  Mounting the message queue filesystem On Linux, message queues are created in a virtual filesystem.  

(Other implementations may also  provide such a feature, but the details are likely to differ.)  This 

file system can be mounted (by the superuser, 注意是使用root用户才能成功) using the following commands:

mkdir /dev/mqueue

mount -t mqueue none /dev/mqueue

还可以使用cat查看该消息队列的状态, rm删除:

cat /dev/mqueue/abc 

rm abc

还可umount该文件系统

umount /dev/mqueue

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80205162
今日推荐