Fork me on GitHub

IPC——消息队列

概述

System V IPC引入了3中通信方式,本文主要介绍消息队列

System V IPC不再以文件的形式存在,因此没有文件描述符这个东西,但是它有类似的“标识符”。完全可以认为这个“标识符”就是文件描述符的替代者,但是它是专门给System V IPC使用的,所以我们不能使用文件IO函数来操作“标识符”,只能使用System V IPC的特有API才能操作。

如何获取这个“标识符”?

调用某API创建好某个“通信结构”以后,API就会返回一个唯一的“标识符”。

 

消息队列原理

消息队列本质

消息队列的本质就是由内核创建的用于存放消息的链表,由于是存放消息的,所以我们就把这个链表称为了消息队列。通信的进程通过共享操作同一个消息队列,就能实现进程间通信。

消息队列模型

每个消息由两部分组成,分别是消息编号(消息类型)和消息正文。
1)消息编号:识别消息用
2)消息正文:真正的信息内容

 

消息队列收发数据

发送消息

①进程先封装一个消息包

这个消息包其实就是如下类型的一个结构体变量,封包时将消息编号和消息正文
写到结构体的成员中。

struct msgbuf
{
    long mtype;             /* 放消息编号,必须> 0 */
    char mtext[msgsz]; /* 消息内容(消息正文) */
}; 

②调用相应的API发送消息

调用API时通过“消息队列的标识符”找到对应的消息队列,然后将消息包发送给消息队列,消息包(存放消息的结构体变量)会被作为一个链表节点插入链表。

接收消息

调用API接收消息时,必须传递两个重要的信息

(a)消息队列标识符
(b)你要接收消息的编号

有了这两个信息,API就可以找到对应的消息队列,然后从消息队列中取出你所要编号的消息,如此就收到了别人所发送的信息。

 

消息队列使用步骤

①使用msgget函数创建新的消息队列、或者获取已存在的某个消息队列,并返回唯一标识消息队列的标识符(msqID),后续收发消息就是使用这个标识符来实现的。

②收发消息

发送消息:使用msgsnd函数,利用消息队列标识符发送某编号的消息
接收消息:使用msgrcv函数,利用消息队列标识符接收某编号的消息

③使用msgctl函数,利用消息队列标识符删除消息队列

对于使用消息队列来通信的多个进程来说,只需要一个进程来创建消息队列就可以了,对于其它要参与通信的进程来说,直接使用这个创建好的消息队列即可。为了保证消息队列的创建,最好是让每一个进程都包含创建消息队列的代码,谁先运行就由谁创建,后运行的进程如果发现它想用的那个消息队列已经创建好了,就直接使用,当众多进程共享操作同一个消息队列时,即可实现进程间的通信。

 

API

msgget

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>                  
int msgget(key_t key, int msgflg); 

功能

利用key值创建、或者获取一个消息队列。key值也能够唯一的标识消息队列。

如果key没有对应任何消息队列,那就创建一个新的消息队列

如果key已经对应了某个消息队列,说明你要的消息队列已经存在了,那就获取这个消息队列来使用

参数

key值:

用于为消息队列生成(计算出)唯一的消息队列ID。

我们可以指定三种形式的key值:

①指定为IPC_PRIVATE宏,指定这个宏后,每次调用msgget时都会创建一个新的消息队列。如果你每次使用的必须是新消息队列的话,就可以指定这个,不过这个用的很少。因为一般来说,只要有一个消息队列可以用来通信就可以了,并不需要每次都创建一个全新的消息队列。

②可以自己指定一个整形数,但是容易重复指定。本来我想创建一个新的消息队列,结果我所指定的这个整形数,之前就已经被用于创建某个消息队列了,当我的指定重复时,msgget就不会创建新消息队列,而是使用的是别人之前就创建好的消息队列。很少使用这种方式

③使用ftok函数来生成key

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,只要路径名和整形数不变,所对应的key值就唯一不变的。不过由于ftok只会使用整形数(proj_id)的低8位,因此我们往往会指定为一个ASCII码值,因为ASCII码值刚好是8位的整形数。

msgflg:

指定创建时的原始权限,比如0664

创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项。msgid = msgget(key, 0664|IPC_CREAT);

如果key值没有对应任何消息队列,就会创建一个新的消息队列,此时就会用到msgflg参数,但是如果key已经对应了某个早已存在消息队列,就直接返回这个已存在消息队列的ID(标识符),此时不会用到msgflg参数。

返回值

成功:返回消息队列标识符(消息队列的ID)对于每一个创建好的消息队列来说,ID是固定的。

失败:失败返回-1,并设置errno。

 

msgsnd

函数原型 

#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:消息队列的标识符。

msgp:存放消息的缓存的地址,类型struct msgbuf类型。这个缓存就是一个消息包(存放消息的结构体变量)。

struct msgbuf
{
    long mtype;         /* 放消息编号,必须 > 0 */
    char mtext[msgsz];  /* 消息内容(消息正文) */
}; 

msgsz:消息正文大大小。

msgflg

0:阻塞发送消息。也就是说,如果没有发送成功的话,该函数会一直阻塞等,直到发送成功为止。

IPC_NOWAIT:非阻塞方式发送消息,不管发送成功与否,函数都将返回也就是说,发送不成功的的话,函数不会阻塞。

返回值

成功:返回0,失败:返回-1,errno被设置

 

msgrcv

函数原型

#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:消息队列的标识符。

msgp:缓存地址,缓存用于存放所接收的消息。类型还是struct msgbuf,同上

msgsz:消息正文的大小

msgtyp:你要接收消息的编号

int msgflg:

0:阻塞接收消息。也就是说如果没有消息时,阻塞接收(msgrcv函数会休眠)。

IPC_NOWAIT:非阻塞接收消息。也就是说没有消息时,该函数不阻塞。他会因为读取失败而错误返回

返回值

成功:返回消息正文的字节数。失败:返回-1,errno被设置

 

msgctl

函数原型 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>                            
int msgctl(int msqid, int cmd, struct msqid_ds *buf); 

功能

根据cmd指定的要求,去控制消息队列。可以有那些控制?

获取消息队列的属性信息

修改消息队列的属性信息

删除消息队列

等等

我们调用msgctl函数的最常见目的就是删除消息队列,事实上,删除消息队列只是各种消息队列控制中的一种。

参数

msqid:消息队列标识符

cmd:控制选项,其实cmd有很多选项,我这里只简单介绍三个

①IPC_STAT:将msqid消息队列的属性信息,读到第三个参数所指定的缓存。

②IPC_SET:使用第三个参数中的新设置去修改消息队列的属性

+ 定一个struct msqid_ds buf。
+ 将新的属性信息设置到buf中
+ cmd指定为IPC_SET后,msgctl函数就会使用buf中的新属性去修改消息队列原有的属性。

③IPC_RMID:删除消息队列.删除消息队列时,用不到第三个参数,用不到时设置为NULL。

buf:存放属性信息

有的时候需要给第三个参数,有时不需要,取决于cmd的设置。buf的类型为struct msqid_ds。

结构体中的成员都是用来存放消息队列的属性信息的。

struct msqid_ds 
{
    struct ipc_perm  msg_perm; /* 消息队列的读写权限和所有者 */
    time_t  msg_stime;             /* 最后一次向队列发送消息的时间*/
    time_t  msg_rtime;             /* 最后一次从消息队列接收消息的时间 */
    time_t  msg_ctime;             /* 消息队列属性最后一次被修改的时间 */
    unsigned  long __msg_cbytes; /* 队列中当前所有消息总的字节数 */
    msgqnum_t  msg_qnum;     /* 队列中当前消息的条数*/
    msglen_t msg_qbytes;        /* 队列中允许的最大的总的字节数 */
    pid_t  msg_lspid;                /* 最后一次向队列发送消息的进程PID */
    pid_t  msg_lrpid;                 /* 最后一次从队列接受消息的进程PID */
};
struct ipc_perm 
{
    key_t          __key;       /* Key supplied to msgget(2):消息队列的key值 */
    uid_t          uid;         /* UID of owner :当前这一刻正在使用消息队列的用户 */
    gid_t          gid;         /* GID of owner :正在使用的用户所在用户组 */
    uid_t          cuid;        /* UID of creator :创建消息队列的用户 */
    gid_t          cgid;        /* GID of creator :创建消息队列的用户所在用户组*/
    unsigned short mode;        /* Permissions:读写权限(比如0664) */
    unsigned short __seq;       /* Sequence number :序列号,保障消息队列ID不被立即重复使用 */
};

返回值

成功:返回0。失败:返回-1,errno被设置

 

多进程共享消息队列

创建进程

如果创建者使用"./file", 'a'生成一个key值,然后调用msgget创建了一个消息队列,比如:

key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);

当创建者得到msgid后,即可操作消息队列。

其他进程共享操作消息队列

共享的方法很简单,只要你能拿到别人创建好的消息队列的ID,即可共享操作同一个消息队列,实现进程间通信。

获取别人创建好的消息队列的ID,有两个方法:

①创建者把ID保存到某文件,共享进程读出ID即可。这种情况下,共享进程根本不需要调用msgget函数来返回ID。

②调用msgget获取已在消息队列的ID

使用ftok函数,利用与创建者相同的“路径名”和8位整形数,生成相同的key值

调用msgget函数,利用key找到别人创建好的消息队列,返回ID

key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);

拿到了消息队列的ID后就能共享操作了。这种方法是最常用的方法,因为ftok所用到的“路径名”和“8位的整形数”比较好记忆,所以,你只要记住别人生成key值时所用的“路径名”和“8位的整形数”,你就一定能共享操作别人创建好的消息队列。

 

 ipcs

用于报告Linux中进程间通信设施的状态,显示的信息包括消息列表、共享内存和信号量的信息。

- a 或者 什么都不跟:消息队列、共享内存、信号量的信息都会显示出来

[root@localhost ~]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 262144     root       600        524288     2          dest         
0x00000000 360449     root       600        16777216   2          dest         
0x00000000 393218     root       600        524288     2          dest         

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

[root@localhost ~]# ipcs -a

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 262144     root       600        524288     2          dest         
0x00000000 360449     root       600        16777216   2          dest         
0x00000000 393218     root       600        524288     2          dest         

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

- m:只显示共享内存的信息  

- q:只显示消息队列的信息

- s:只显示信号量的信息

 

ipcrm

用来删除一个或更多的消息队列、信号量集或者共享内存标识。

删除共享内存

M:按照key值删除
ipcrm -M key

m:按照标识符删除
ipcrm -m msgid

删除消息队列

Q:按照key值删除
q:按照标识符删除

删除信号量

S:按照key值删除
s:按照标识符删除

 

消息队列示例代码

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/ipc.h>
  6 #include <sys/msg.h>
  7 #include <sys/types.h>
  8 #include <sys/stat.h>
  9 #include <fcntl.h>
 10 #include <strings.h>
 11 #include <signal.h>
 12 
 13 #define MSG_FILE "./msgfile"
 14 
 15 #define MSG_SIZE 1024
 16 
 17 struct msgbuf
 18 {
 19     long mtype;         /* 放消息编号,必须 > 0 */
 20     char mtext[MSG_SIZE];  /* 消息内容(消息正文) */
 21 };                                    
 22 
 23 
 24 void print_err(char *estr)
 25 {
 26     perror(estr);
 27     exit(-1);
 28 }
 29 
 30 int creat_or_get_msgque(void)
 31 {
 32     int msgid = -1;
 33     key_t key = -1;
 34     int fd = 0;
 35 
 36     /* 创建一个消息队列的专用文件,ftok会用到这个文件的路径名 */
 37     fd = open(MSG_FILE, O_RDWR|O_CREAT, 0664);
 38     if(fd == -1) print_err("open fail");
 39     
 40     /* 利用存在的文件路径名和8位整形数,计算出key */
 41     key = ftok(MSG_FILE, 'a');
 42     if(key == -1) print_err("ftok fail");
 43 
 44     /* 利用key创建、或者获取消息队列 */
 45     msgid = msgget(key, 0664|IPC_CREAT);
 46     if(msgid == -1) print_err("msgget fail");
 47 
 48     return msgid;
 49 }    
 50 
 51 int msgid = -1;
 52 void signal_fun(int signo)
 53 {
 54     msgctl(msgid, IPC_RMID, NULL);
 55     remove(MSG_FILE);    
 56     
 57     exit(-1);
 58 }
 59 
 60 int main(int argc, char **argv)
 61 {    
 62     int ret = -1;
 63     long recv_msgtype = 0;
 64     
 65     if(argc !=  2)
 66     {
 67         printf("./a.out recv_msgtype\n");
 68         exit(-1);
 69     }
 70     //atol字符串转长整形
 71     recv_msgtype = atol(argv[1]);
 72     
 73     
 74     msgid = creat_or_get_msgque();
 75 
 76     ret = fork();
 77     if(ret > 0) //发送消息
 78     {
 79         signal(SIGINT, signal_fun);
 80         struct msgbuf msg_buf = {0};
 81         while(1)
 82         {
 83             bzero(&msg_buf, sizeof(msg_buf));
 84             /* 封装消息包 */
 85             scanf("%s", msg_buf.mtext);
 86             printf("input snd_msgtype:\n");
 87             scanf("%ld", &msg_buf.mtype);
 88 
 89             /* 发送消息包 */
 90             msgsnd(msgid, &msg_buf, MSG_SIZE, 0);    
 91         }
 92     }
 93     else if(ret == 0)//接收消息
 94     {
 95         struct msgbuf msg_buf = {0};
 96         int ret = 0;
 97         while(1)
 98         {
 99             bzero(&msg_buf, sizeof(msg_buf));
100             ret = msgrcv(msgid, &msg_buf, MSG_SIZE, recv_msgtype, 0);
101             if(ret > 0) 
102             {
103                 printf("%s\n", msg_buf.mtext);
104             }    
105         }
106     }
107     
108         
109     return 0;
110 }

 

 

posted @ 2018-07-30 19:54  克拉默与矩阵  阅读(1274)  评论(0编辑  收藏  举报