目录
一、消息队列的定义和特点
1.什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题(命名管道要读端和写端都存在,否则出现阻塞)。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
2.消息队列的特点
-
消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取;
-
消息队列允许一个或多个进程向它写入或者读取消息;
-
与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除;
-
每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的;
-
消息队列是消息的链表,存放在内存中,由内核维护。只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中。
3.抽象的理解消息队列
对于消息队列的操作,我们可以类比为这么一个过程:假如 A 有个东西要给 B,因为某些原因 A 不能当面直接给 B,这时候他们需要借助第三方托管(如银行),A 找到某个具体地址的建设银行,然后把东西放到某个保险柜里(如 1 号保险柜),对于 B 而言,要想成功取出 A 的东西,必须保证去同一地址的同一间银行取东西,而且只有 1 号保险柜的东西才是 A 给自己的。
而在消息队列操作中,键(key)值相当于地址,消息队列标示符相当于具体的某个银行,消息类型相当于保险柜号码。
同一个键(key)值可以保证是同一个消息队列,同一个消息队列标示符才能保证不同的进程可以相互通信,同一个消息类型才能保证某个进程取出是对方的信息。
二、消息队列的操作函数
1.函数一_获取key的值
在消息队列中,进程间通信机制需要一个 key 值,通过 key 值就可在系统内获得一个唯一的消息队列标识符。key 值可以是人为指定的,也可以通过 ftok() 函数获得。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- 功能:获取键(key)值。
- 参数:
- pathname: 路径名;
- proj_id: 项目ID,非 0 整数(虽然为int,但是只有8个比特被使用(0-255)。);
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为 0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i
- 返回值:成功返回key 值,失败返回-1。
2.函数二_消息队列的消息格式
typedef struct _msg
{
long mtype; // 消息类型
char mtext[100]; // 消息正文
//…… …… // 消息的正文可以有多个成员
}MSG;
消息类型必须是长整型的,而且必须是结构体类型的第一个成员,类型下面是消息正文,正文可以有多个成员(正文成员可以是任意数据类型的)。至于这个结构体类型叫什么名字,里面成员叫什么名字,自行定义,没有明文规定。
3.函数三_创建或打开一个消息队列
该函数用来打开一个现存的队列(当该消息队列存在时)或者创建一个消息队列。它的原型为:
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
- 功能:创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的 key 值就能得到同一个消息队列的标识符。
- 参数:
- key:ftok() 返回的 key 值。
- msgflg:标识函数的行为及消息队列的权限,其取值如下:
- IPC_CREAT:创建消息队列;
- IPC_EXCL: 检测消息队列是否存在;
- 位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,格式和open() 函数的 mode_t 一样(open() 的使用请点此链接),但可执行权限未使用。
- 返回值:成功:消息队列的标识符,失败:-1。
4.函数四_向消息队列中添加消息
#include <sys/msg.h>
int msgsnd( int msqid, const void *msgp, size_t msgsz, int msgflg);
- 功能:将新消息添加到消息队列。
- 参数:
- msqid: 消息队列的标识符;
- msgp: 待发送消息结构体的地址;
- msgsz: 消息正文的字节数;
- msgflg:函数的控制属性,其取值如下:
- 0:msgsnd() 调用阻塞直到条件满足为止。
- IPC_NOWAIT:若消息没有立即发送则调用该函数的进程会立即返回。
- 返回值:成功:0,失败:-1。
5.函数五_从消息队列中取出消息
#include <sys/msg.h>
ssize_t msgrcv( int msqid, void *msgp, size_t msgsz,
long msgtyp, int msgflg );
- 功能:从标识符为 msqid 的消息队列中接收一个消息。一旦接收消息成功,则消息在消息队列中被删除。
- 参数:
- msqid:消息队列的标识符,代表要从哪个消息列中获取消息;
- msgp: 存放消息结构体的地址;
- msgsz:消息正文的字节数;
- msgtyp:消息的类型。可以有以下几种类型:
- msgtyp = 0:返回队列中的第一个消息;
- msgtyp > 0:返回队列中消息类型为 msgtyp 的消息(常用);
- msgtyp < 0:返回队列中消息类型值小于或等于 msgtyp 绝对值的消息,如果这种消息有若干个,则取类型值最小的消息(在获取某类型消息的时候,若队列中有多条此类型的消息,则获取最先添加的消息,即先进先出原则)。
- msgflg:函数的控制属性。其取值如下:
- 0:msgrcv() 调用阻塞直到接收消息成功为止;
- MSG_NOERROR: 若返回的消息字节数比 nbytes 字节数多,则消息就会截短到 nbytes 字节,且不通知消息发送进程;
- IPC_NOWAIT: 调用进程会立即返回。若没有收到消息则立即返回 -1。
- 返回值:成功:读取消息的长度,失败:-1。
6.函数六_对消息队列进行控制
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 功能:对消息队列进行各种控制,如修改消息队列的属性,或删除消息消息队列。
- 参数:
- msqid: 消息队列的标识符;
- cmd:函数功能的控制。其取值如下:
- IPC_RMID:删除由 msqid 指示的消息队列,将它从系统中删除并破坏相关数据结构;
- IPC_STAT:将 msqid 相关的数据结构中各个元素的当前值存入到由 buf 指向的结构中。相对于,把消息队列的属性备份到 buf 里;
- IPC_SET:将 msqid 相关的数据结构中的元素设置为由 buf 指向的结构中的对应值。相当于,消息队列原来的属性值清空,再由 buf 来替换。
- buf:msqid_ds 数据类型的地址,用来存放或更改消息队列的属性。
- 返回值:成功:0,失败:-1。
三、【demo】
// MsgSend.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_TEXT 512
struct msg_st
{
long int msg_type;
char text[MAX_TEXT];
};
int main()
{
int running = 1;
struct msg_st data;
char buffer[BUFSIZ];//BUFSIZ为stdio.h中的一个宏,即#define _G_BUFSIZ 8192
int msgid = -1;
// 建立消息队列, key_t为标识符
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1) // 消息队列建立失败
{
perror("msgget");
exit(1);
}
// 向消息队列中写消息,直到写入end
while(running)
{
// 输入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin); // 从标准输入读取数据
data.msg_type = 1; // 消息类型
strcpy(data.text, buffer); // 把标准输入复制到msg_st的text
// 向队列发送数据
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
perror("msgsnd");
exit(1);
}
// 输入end结束输入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
return 0;
}
// MsgReceive.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid = -1;
struct msg_st data;
long int msgtype = 0; // 指定想要哪一种消息,type>0表示取第一个
//建立消息队列 key_t为标识符
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
perror("msgget");
exit(1);
}
//从队列中获取消息,直到遇到end消息为止
while(running)
{
if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
{
perror("msgrcv");
exit(1);
}
printf("You wrote: %s\n", data.text);
//遇到end结束
if(strncmp(data.text, "end", 3) == 0)
running = 0;
}
//删除消息队列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
perror("msgctl");
exit(1);
}
return 0;
}
【执行结果】
四、消息队列的优缺点
【优点】:
-
消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难;
-
同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法(空队列不可读,满队列不可写,不发则不收);
-
接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
【缺点】:
- 信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合。