进程间通信——XSI IPC之消息队列

 

 进程间通信XSI IPC有3种:消息队列、共享内存、信号量。它们之间有很多相似之处,但也有各自的特殊的地方。消息队列作为其中比较简单的一种,它会有些什么东西呢,来一起探讨探讨。。

 

消息队列结构  


消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法. 每个数据块都被认为是一个类型,接受进程接收的数据块可以有不同的类型值。

我们可以通过发送消息来避免命名管道的同步和阻塞问题。 消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出.

 

命名管道:

      

消息队列:

  

 

消息队列与命名管道有一个不足,就是每个消息最大长度是有上限的.而且还注意消息队列的生命周期是伴随内核的. 

所以如果你没有显示的删除它,那么在关机前它一直在.这里的消息队列我们可以看成一个链表队列,具体为什么? 让我们来看看消息队列的结构.

 

消息队列结构:

tp@tp:  more  /usr/include/linux/msg.h

 

此结构定义了队列的当前状态。这里的_first和_last便说明队列的链式结构,它们分别是消息队列的头、尾指针。

初此以外,我们可能还会疑问这个结构是如何表示出这个消息队列的?举个例子,就是我要找出消息队列的"身份证".不妨调出 struct msqid_ds结构体的第一个条目,

也就是IPC的共有结构体,我们再看看它的结构

 

    

该结构体中第一个成员key,其实就是消息队列的唯一标识,想找到一个消息队列就是靠它来找,这个我们待会会说到的。

 

 

基本命令


 

系统能创建多少个消息队列?

  使用cat  /proc/sys/kernel/msgmni查看

每个消息队列能装多少个字节?

  用cat  /proc/sys/kernel/msgmnb查看

队列中每一条基类最大是多大?

   用cat  /proc/sys/kernel/msgmax查看

 

显示消息队列

  ipcs -q      //s:status

  

   

   ipcs (SystemV IPC系列都可以看)

  

 

接下来手工删除

  ipcrm -q msqid   (此例对应65536)

 

 

 

 相关函数


1.创建消息队列

  原型:int msgget(key_t key,     int  msgflg )

  返回值:失败返回-1, 个数限制, 打开别人队列没权限。大于0  调用成功  返回已打开的消息队列ID标识。

参数:

  key_t key    //相当于文件名,也可以由函数ftok生产

   int  msgflg   //指定消息队列创建。一般是IPC_CREAT  0644  或者0 (为0 则代表由操作系统自动选择)     

  

2.往消息队列中写数据

  原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)

参数:

  void* msgp     //想写入消息队列数据的地址,是指向消息缓冲区的指针,此位置用来暂时存储发送接收消息,是一个用户可定义的通用结构,形态如下:

 

struct msgbuf
{

  long mtype;       //k接收类型,可看作消息队列通道号channel   ,同时必须> 0

  char mtext[100]   //存放内容
};

 

 

 

  size_t msgsz   //消息大小, 不算上通道号大小

  int msgflg     //一般输入0,由操作系统自行选择

 

 

3.从消息队列中读数据

 原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)

 

参数:

  void *msgp      //接收的消息放在那里

  size_t msgsz    //接收消息的地方大小,不算上通道号

  long msgtyp    //从消息队列内读取的消息形态(选择何种通道)。值为0,表示消息队列中的所有消息都读取

  int msgflg      //0

 

注意msgflg这个参数,它用来核心程序在队列没有数据的情况的下所采取的行动.

如果msgflg和常数IPC_NOWAIT合用,在msgsnd()执行时若是消息队列已满,则msgsnd()不会阻塞,而会立即返回-1,

如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码ENOMSG。

当msgflg为0时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理方式.

 

 

 

4.设置消息队列属性

  原型:int msgctl(int msqid,int cmd, struct msqid_ds *buf)

 

参数:

 

 int cmd      

msgctl系统调用函数对msgqid标识的消息队列执行cmd设置。操作系统定义了常见的3种cmd操作:

 

  IPC_STAT:该命令用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间.

 

  IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中.

 

  IPC_RMID:从内核中删除msqid标识的消息队列.函数调用删除消息队列

  

struct msqid_ds *buf);   //cmd不是IPC_STAT 可设置为0,自动选择

 

消息队列应用


小练习:

创建客户端(client)和服务端(server) ,使得不同的client端可以通过消息队列向server端发送请求,server端分别对client做出回应。

思路:大致如图 (channel 相当于消息类型)

 

          

 

实现代码:

  server.c

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/msg.h>
  5 #include<string.h>
  6 #include<unistd.h>
  7 struct msgbuf{
  8     long channel ;//通道号
  9     char mtext[100];
 10 };
 11 
 12 
 13 int main(void)
 14 {
 15     int id = msgget(12345, IPC_CREAT|0600); //创建消息队列
 16     if(id == -1) perror("msgget"),exit(1);
 17 
 18     while(1)
 19     {
 20         struct msgbuf mb;
 21         memset(&mb, 0x00, sizeof(mb));
 22         if(msgrcv(id ,&mb, 100,1, 0) == -1) //从消息队列接收client端送来数据
 23             perror("msgrcv"),exit(1);
 24 
 25         printf("Get from Client:%s\n", mb.mtext+ sizeof(long));
 26 //      fflush(stdout);
 27         mb.channel = *(long*)(mb.mtext); //将头4个字节强转赋值给队列通道号
 28         //发送回消息队列
 29         msgsnd(id, &mb, strlen(mb.mtext+sizeof(long)) + sizeof(long), 0);
 30     }
 31 
 32 }
 33 
server.c

  client.c

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/msg.h>
  5 #include<string.h>
  6 #include<unistd.h>
  7 
  8 struct msgbuf
  9 {
 10     long channel;  //消息队列通道号
 11     char mtext[100];
 12 };
 13 
 14 int main(void)
 15 {
 16     int id = msgget(12345, 0);
 17     if(id == -1) perror("msgget"),exit(1);
 18     while(1)
 19     {
 20         struct msgbuf mb,rcv;
 21         memset(&mb, 0x00, sizeof(mb));
 22         //从stdin读入数据到mb.mtext后移sizeof(long)字节出
 23         while(fgets(mb.mtext+sizeof(long), 100- sizeof(long), stdin) != NULL)
 24         {
 25             *(long*)mb.mtext = getpid(); //头部赋值为pid
 26             mb.channel = 1;  //选用通道号 1
 27 
 28             //发送到消息队列. 注意对发送大小的处理
 29             msgsnd(id, &mb, strlen(mb.mtext+ sizeof(long))+sizeof(long), 0);
 30             printf("send ok!\n");
 31 
 32             //从消息队列获取
 33             memset(&rcv, 0x00, sizeof(rcv));  //清空
 34             if(msgrcv(id, &rcv, 100, (long)getpid(), 0) ==-1)
 35                 perror("msgrcv"),exit(1);
 36             printf("Message back: %s\n", rcv.mtext + sizeof(long));
 37         }
 38     }
 39     return 0;
 40 }
client.c

 

相关makefile

                                 

 

大致使用这些函数便简单地实现了双向通信,效果就像这样

 

 

 

 

 

最后总结,消息队列相对命名管道优势。

消息队列与命名管道相比,消息队列的优势在于:


  1.消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。

  2.基于发送消息机制,避免了像命名管道存在的同步、阻塞问题,不需要由进程自己来提供同步方法。

  3.接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样默认地接收数据。
 
  4.实现了双向通信

 

posted @ 2018-04-30 14:06  tp_16b  阅读(1186)  评论(0编辑  收藏  举报