进程间的通讯(ipc)-----消息队列

一、什么是消息队列?

IPC:inter-project communication(进程间的通信)
在linux中IPC有三种:消息队列、共性内存、信号量。
消息队列实际就是一个消息的列表,进程可以在消息队列中添加消息和的读取消息。消息队列具有FIFO的特性,具有无名管道与有名管道各自的优势,可以支持任意两个进程的进程间通讯。想了解管道的可以看我前面的博客。
在linux终端下可以使用 ipcs -q或者ipcs 来查看现有的消息队列。

每一个消息队列都有唯一的msqid。

下面的我们来看一下有关消息队列的函数。

二、消息队列的函数

1.消息队列的创建函数

函数头文件                 #include <sys/types.h>
                                   #include <sys/ipc.h>
                                   #include <sys/msg.h>
函数原型                     int msgget(key_t key, int msgflg);
函数功能                     获取System V消息队列标识符
函数参数
                                    key:由ftok函数合成
                                    msgflg:消息队列标志
                                    IPC_CREAT 创建标志
                                    IPC_EXCL 如果消息队列存在,则报错, errno设置为EEXIST权限控制
                                    标志
函数返回值                 成功:返回消息队列id
                                   失败:返回-1,并设置errno

消息队列的创建函数返回的类型是一个int 类型,一个id号,如果要在这个消息队列发送或者接收消息,就需要使用这个id号。当然我们可以创建多个消息队列,因为消息队列的大小是有限的。

在创建这个函数的时候需要先使用fotk()函数,得到一个key_t类型的值,fotk()函数使用来创建一个IPC,包括后面的共享内存、信号量,都需要使用先fotk()函数,不了解的fotk()函数没关系,后面有示例。

2.消息队列的删除

函数头文件                  #include <sys/types.h>
                                    #include <sys/ipc.h>
                                    #include <sys/msg.h>
函数原型                     int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数参数
                                    msqid:消息队列 id
                                    cmd:命令字
                                    IPC_STAT:获取消息队列属性
                                    IPC_SET:设置消息队列属性
                                    IPC_RMID:删除消息队列,用此命名时,第三个参数为NULL
                                    buf:消息队列属性结构体对象指针
函数返回值
                                    成功:IPC_STAT, IPC_SET, and IPC_RMID 返回0
                                    失败:返回-1,并设置 errno

在后面的实例中,仅使用函数去删除消息队列,并没有获取相对应的属性。如果需要获取消息队列的属性,那就需要定义struct msqid_ds结构体,通过结构体来获取属性。 

struct msqid_ds结构体的定义:

       struct msqid_ds {
           struct ipc_perm msg_perm;   /* Ownership and permissions */
           time_t          msg_stime;  /* Time of last msgsnd(2) */
           time_t          msg_rtime;  /* Time of last msgrcv(2) */
           time_t          msg_ctime;  /* Time of creation or last
                          modification by msgctl() */
           unsigned long   msg_cbytes; /* # of bytes in queue */
           msgqnum_t       msg_qnum;   /* # number of messages in queue */
           msglen_t        msg_qbytes; /* Maximum # of bytes in queue */
           pid_t           msg_lspid;  /* PID of last msgsnd(2) */
           pid_t           msg_lrpid;  /* PID of last msgrcv(2) */ 

         };

再来看一下 定义个 struct ipc_perm是什么类型:

       struct ipc_perm {
           key_t          __key;       /* Key supplied to msgget(2) */
           uid_t          uid;     /* Effective UID of owner */
           gid_t          gid;     /* Effective GID of owner */
           uid_t          cuid;    /* Effective UID of creator */
           gid_t          cgid;    /* Effective GID of creator */
           unsigned short mode;    /* Permissions */
           unsigned short __seq;       /* Sequence number */                                                                        
       }; 

可以看到上面的两个结构体中定义的大都是一些时间信息和ID,现在不用太关注这些信息。

3.消息队列的发送和接收

函数头文件                      #include <sys/types.h>
                                        #include <sys/ipc.h>
                                        #include <sys/msg.h>
函数原型
                                        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数参数:
                                        msqid:消息队列ID
                                        msgp:消息结构体指针
                                        msgsz:消息内容的长度
                                        msgflg:消息队列标志,默认可以填0,IPC_NOWAIT,可以设置非阻塞
函数返回值
                                        成功:返回0
                                        失败:-1,并设置errno

 这里参数需要注意的是msgp是结构体指针不是发送数组的指针,这个参数填错会导致进程卡死。

msgsz可以通过sizeof()来获取长度。在后面的实例中msgflg是设置的阻塞,所以这个参数填0。

函数头文件                      #include <sys/types.h>
                                        #include <sys/ipc.h>
                                        #include <sys/msg.h>
函数原型                          ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
函数参数
                                        msqid:消息队列id
                                        msgp:消息结构指针
                                        msgsz:最大读取的字节数
                                        msgtyp:消息的标识
                                        msgflg:消息队列标志,默认可以填0,IPC_NOWAIT,可以设置非阻
                                                     塞。
函数返回值                       成功:返回实际读取消息内容的字节数
                                         失败:-1,并设置 errno

 接收相较于发送只是多了一个参数,是第四个参数msgtyp,这个参数非常关键,它是一个标识,long类型,用来保证接收是正确的消息。这个标识在初始化的时候就已经确定,就好比连连看,只有标识相同才可以接收到对应的消息。

三.消息队列的的使用

下面是消息队列的简单示例:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <error.h>
  5 #include <sys/types.h>
  6 #include <sys/ipc.h>
  7 #include <sys/msg.h>
  8 #include <sys/wait.h>
  9 #include <unistd.h>
 10 
 11 #define PATH    "/home/linux/"
 12 #define ID      10
 13 
 14 struct mbuf{
 15     long mtype; //作为接受消息的标识;                                                                                                                                                                                                                                                                                                                                                                    
 16     char buf[64];   //存给消息的数组;
 17 };
 18 
 19 int main()
 20 {
 21     //定义一个结构体类型的数组;
 22     struct mbuf data[4]={0};
 23     /*
 24      *在结构体数组中有两个的mtype相同;
 25      *数组索引0为发送端,1为接受端;
 26      *数组索引2为发送端,3为接受端;
 27      */
 28     data[0].mtype = 1;
 29     strcpy(data[0].buf,"hello first process!");
 30     data[1].mtype = 1;
 31 
 32     data[2].mtype = 2;
 33     strcpy(data[2].buf,"hello sencnd process!");
 34     data[3].mtype = 2;
 35     key_t ipcs = ftok(PATH,ID);                /* 创建ipcs*/
 36     if(ipcs == -1){
 37         perror("ftok");
 38         exit(1);
 39     }
 40     int id = msgget(ipcs,IPC_CREAT | 0666);   /* 创建消息队列*/
 41     if(id == -1){
 42         perror("msgget");
 43         exit(1);
 44     }
 45     pid_t pid_1 = fork();                       /* 创建一个子进程*/
 46     if(pid_1 == -1){
 47         perror("fork 1");
 48         exit(1);
 49     }else if(pid_1 == 0){                       /* 子进程部分*/
 50         sleep(1);                               /* 延时一秒,确保主进程先执行*/
 51         ssize_t rbytes =  msgrcv(id,&data[1],sizeof(data[1].buf),data[1].mtype,0);/* 读取消息队列中标识是 1 的消息 */
 52         if(rbytes == -1){
 53             perror("msgrcv 1");
 54             exit(1);
 55         }
 56         printf("data[1].buf = %s\n",data[1].buf);
 57         exit(EXIT_SUCCESS);
 58     }else{                                      /* 主进程部分*/
 59         pid_t pid_2 = fork();                   /* 创建第二个子进程*/
 60         if(pid_2 == -1){
 61             perror("fork 2");
 62             exit(1);
 63         }else if(pid_2 == 0){                   /* 第二个子进程部分*/
 64             sleep(2);                           
 65             ssize_t rbytes_2 =  msgrcv(id,&data[3],sizeof(data[3].buf),data[3].mtype,0);/* 读取标识为 2 的消息*/
 66             if(rbytes_2 == -1){
 67                 perror("msgrcv 2");
 68                 msgctl(id, IPC_RMID, NULL);
 69                 exit(1);
 70             }
 71             printf("data[3].buf = %s\n",data[3].buf);
 72             exit(EXIT_SUCCESS);
 73         }else{                                  /* 主进程成部分*/
 74             printf("parent start\n");
 75             int snd = msgsnd(id,&data[0],sizeof(data[0].buf),0);/* 向消息队列中发送信息*/
 76             if(snd == -1){
 77                 perror("msgsnd 1");
 78                 msgctl(id, IPC_RMID, NULL);
 79                 exit(1);
 80             }
 81             int snd_2 = msgsnd(id,&data[2],sizeof(data[2].buf),0);/* 消息队队列中发送信息*/
 82             if(snd == -1){
 83                 perror("msgsnd 2");
 84                 msgctl(id, IPC_RMID, NULL);
 85                 exit(1);
 86             }
 87             wait(NULL);                                           /* 等待两个子进程结束*/
 88             wait(NULL);
 89             int ctl = msgctl(id,IPC_RMID,NULL);                   /* 删除消息队列*/
 90             if(ctl == -1){
 91                 perror("msgctl");
 92                 msgctl(id, IPC_RMID, NULL);
 93                 exit(1);
 94             }
 95             printf("parent end\n");
 96         }
 97     return 0;
 98     }
 99 }      

 运行结果如下:

 可以看到,已经成功接收到了数据。


总结

以上的内容就是有关消息队列的内容了,有发现问题的可以提出来,共同进步。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值