一、前言:
前面讲过,进程间管道的通讯,信号量控制的通讯,以及一开始的信号,这些都是进程的通讯方式。信号通过响应某些条件产生事件,使得另一进程(即接收信号的进程)做出相应的反应。管道是通过一端写,另一端读的方式进行通讯,即所谓的点对点通讯。信号量是控制进程间对临界资源访问的一个计数器,来进行同步通讯。那么今天所说的是另外一种通讯方式-----消息队列。
二、消息队列的理论及特点
与之前的点对点通讯相比较,消息队列得到了很好的优化,为什么这么说呢?消息队列可以定向的发送数据。那么怎么定向发送数据这就要从它的内部传输数据开始分析了,它内部所传输的数据,也就是消息,包含以下两个元素:
消息(数据块) : 数据 ------+--------类型
所以消息队列就是一个发送带有数据类型的数据,以及接受的进程可以通过类型获得指定的数据,并且这些类型的数据遵循队列的原则,即先进先出。
即然是队列,那么消息队列就有它的排队原则:
最简单的排队原则就是上述所说的先进先出原则,但是当某些消息比较紧急的时候这先进先出原则就显得不够用了,一个可选的原则就是指定消息的优先级,这可以基于消息的类型或者发送者指定,另一种选择就是接受者检查消息队列选出下一条所要接收的信息(即通过类型指定)。
消息队列是一列具有头和尾的队列,新来的信息放在队列尾部,而读取的数据从队列的头部读取。
如图所示:
没说过消息队列之前看这个是不是感觉和管道很像,但是现在一看有数据类型,明显就是一个消息队列,并且操作的都是进程,对吧。但是这与管道的不同不止在类型区别,管道只能一端写,另一端写读,但是消息队列就不一样了,它没有固定的读写进程,任何有权限的进程都可以进行读写。并且消息队列还支持多进程进行操作,即多进程进行读写消息队列,即所谓的多对多,与管道的点对点是不一样的。另外消息队列只在内存中实现,与管道进行比较还发现消息队列避免了在管道中的阻塞和同步的问题。但是与管道一样,每个数据块都有一个最大长度的限制。系统中所有队列所包含的全部数据块也有一个长度上限。
三、消息队列的操作:
与信号量一样,它的实现也是由底层的操作系统内核实现完成的 ,我们只需要调用底层的函数来实现相应的需求即可,由于消息队列底层的函数比较具体,所以我们不需要对其进行封装。
首先引用头文件:
#include
与信号量一样,头文件sys/types.h和sys/ipc.h被msg.h自动包含进程序。
msgget()函数:创建或者获取
创建和访问一个消息队列;
int msgget(key_t key, int msgflg);
第一个参数key_t key:与其他的IPC机制一样,程序需要一个键值来命名某个特定的消息队列,特殊键值IPC_PRIVATE用于创建私有队列,创建的队列不是私有的,私有的话没有太大的操作意义。
第二个参数 msgflg:由9个权限标志组成;由ICP_CREAT定义的一个特殊的位必须和权限标志控制按位或才能创建一个新的消息队列。如果消息队列已经存在,当再创建一个已有的消息队列时,不会产生错误,并且会把IPC_CREAT悄悄的忽略掉。
返回值:成功时返回一个正整数,即队列标识符,失败返回-1.
msgsnd函数:发送数据(写数据)
用来把消息添加到消息队列中
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
消息的结构受两方面的限制:一是长度小于系统所给的上限,二是定义的成员变量必须是以长整形开始,接收函数是以这个成员变量来确定消息的类型。
消息结构体定义如下:
struct my_message
{
long int message_type;//消息类型来确认
//date 就是你所要传输的数据
}
第一个参数:msgid是msgget()函数的返回值,即队列标识符;
第二个参数:msg_ptr是指向消息这个结构体的指针;
第三个参数:msg_sz是消息结构体中数据的长度,千万记住这个长度不包括长整形类型成员变量
第四个参数:msgflg是控制当前消息队列满或者队列消息达到系统所给的上限的发生的事情。如果msgflg设置了IPC_NOWAIT标志,函数将立即返回,不发送消息并返回值为-1,如果msgflg中的IPC_NOWAIT标志被清除,则发送进程将挂起以等待队列中腾出可用空间。
返回值:成功返回0,失败返回-1,如果调用成功,消息数据的一份副本将被放到消息对列中。
msgrcv()函数:接收数据(读取数据)
从一个消息队列中获取消息;
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
第一个参数:msgid是msgget()函数的返回值,即队列标识符;
第二个参数:msg_ptr是指向准备接收消息这个结构体的指针;
第三个参数:msg_sz是准备接收消息结构体中数据的长度,不包括长整形类型成员变量;
第四个参数:msgtype是一个长整数,实现简单形式的接收优先级,如果它的值为0,就获取队列中的第一个可用消息,如果它的值大于0,将获得消息队列中与它相同类型的第一个消息,如果它的值小于0,将获取消息类型等于或者小于它的绝对值的第一个消息。
第五个参数:msgflg是用于控制队列中没有相应的类型的消息可以接收将发生的事情,如果msgflg设置了IPC_NOWAIT标志,函数将立即返回,返回值为-1,如果msgflg中的IPC_NOWAIT标志被清除,进程将会挂起以等待一条相应的类型的消息到达。
返回值:成功返回接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息,失败时返回-1;
msgctl函数:删除消息队列;
int msgctl(int msgid, int command, struct msgid_ds *buf);
msgid_ds 结构包含以下成员:
struct msgid_ds
{
uid_t msg_perm.uid;
uid_t msg_perm.gid;
mode_t msg_perm.mode;
}
第一个参数:msgid是msgget返回的消息队列标识符;
第二个参数:command是将要采取的动作,它可以取以下三个值:
IPC_STAT :把msgid_ds结构中的数据设置为消息队列的当前关联值;
IPC_SET :如果进程有足够权限,就把消息队列设置为msgid_ds结构中给出的值;
IPC_RMID : 删除消息队列;
返回值:成功返回0,失败返回-1,如果删除队列,某个进程正在msgsnd或msgrcv函数中等待,这两个函数将失败。
Linux下消息队列、共享内存、信号量命令:
查找命令:
ipcs -q 查询消息队列
ipcs -s 查询信号量
ipcs -m 查询共享内存
删除命令:
ipcrm -q msgid 删除消息队列
ipcrm -s semid 删除信号量
ipcrm -m shmid 删除共享内存
四、练习
要求:实现进程A通过类型“1”读出 my ,进程B通过类型“2”读出 baby;
实现代码如下:
写数据:my 和 baby
msga.c文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/msg.h>
struct msgbuff
{
long type;
char data[128];
};
void main()
{
int msgid = msgget((key_t)1234,0666 | IPC_CREAT);
assert(msgid != -1);
struct msgbuff buff;
buff.type = 1;
strcpy(buff.data,"my");
msgsnd(msgid,&buff,strlen(buff.data),0);
buff.type = 2;
memset(buff.data,0,128);
strcpy(buff.data,"baby");
msgsnd(msgid,&buff,strlen(buff.data),0);
exit(0);
}
读取数据:msgb.c文件
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
struct msgbuff
{
long type;
char data[128];
};
int main(int argc,char *argv[])
{
long type = 0;
if (argv[1] != NULL)
{
sscanf(argv[1],"%d",&type);
int msgid = msgget((key_t)1234,0666 | IPC_CREAT);
assert(msgid != -1);
struct msgbuff buff;
memset(&buff,0,sizeof(buff));
msgrcv(msgid,&buff,sizeof(buff),type,0);
printf("%s\n",buff.data);
}
else
{
exit(0);
}
}
运行结果及分析:
运行./msga之前:
运行./msga之后:
used-bytes这与我们的写入my baby的字节刚刚好相等。
运行./msgb:读取数据,my 和 baby
成功读取出来,并且是定向的接收数据。
那么我们再看一下消息队列里面是否还有数据:
显示已经没有数据了。