消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。消息队列存放在内核中并由消息队列标识符标识,是一种链表队列。
我们可以通过发送消息来避免命名管道的同步和阻塞问题。但消息队列与命名管道一样,每个数据块都有一个最大长度的限制。我们可以将每个数据块当作是一种消息类型(频道),发送和接收的内容就是这个类型(频道)对应的消息(节目),每个类型(频道)相当于一个独立的管道,相互之间互不影响。Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V IPC机制,即信号量和共享内存相似。
MQ 传递的是消息,消息即是我们需要在进程间传递的数据。MQ 采用链表来实现消息队列,该链表是由系统内核维护,系统中可能有很多的 MQ,每个 MQ 用消息队列描述符(消息队列 ID:qid)来区分,qid 是唯一的,用来区分不同的 MQ。在进行进程间通信时,一个进程将消息加到 MQ 尾端,另一个进程从消息队列中取消息(不一定以先进先出来取消息,也可以按照消息类型字段取消息),这样就实现了进程间的通信。如下 MQ 的模型:
进程 A 向内核维护的消息队列中发消息,进程 B 从消息队列中取消息,从而实现了 A 和 B 的进程间通信。
Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
struct msqid_ds
{
struct ipc_perm msg_perm; //消息队列访问权限
struct msg *msg_first; //指向第一个消息的指针
struct msg *msg_last; //指向最后一个消息的指针
ulong msg_cbytes; //消息队列当前的字节数
ulong msg_qnum; //消息队列当前的消息个数
ulong msg_qbytes; //消息队列可容纳的最大字节数
pid_t msg_lsqid; //最后发送消息的进程号ID
pid_t msg_lrqid; //最后接收消息的进程号ID
time_t msg_stime; //最后发送消息的时间
time_t msg_rtime; //最后接收消息的时间
time_t msg_ctime; //最近修改消息队列的时间
};
struct msg_queue {
structkern_ipc_perm q_perm;
time_tq_stime; /* last msgsndtime */
time_tq_rtime; /* last msgrcvtime */
time_tq_ctime; /* last changetime */
unsignedlong q_cbytes; /* current number of bytes on queue*/
unsignedlong q_qnum; /* number of messages inqueue */
unsignedlong q_qbytes; /* max number of bytes on queue */
pid_tq_lspid; /* pid oflast msgsnd */
pid_tq_lrpid; /* lastreceive pid */
structlist_head q_messages;
structlist_head q_receivers;
structlist_head q_senders;
};
struct ipc_perm
{
key_t key; //调用shmget()时给出的关键字
uid_t uid; //共享内存所有者的有效用户ID
gid_t gid; //共享内存所有者所属组的有效组ID
uid_t cuid; //共享内存创建 者的有效用户ID
gid_t cgid; //共享内存创建者所属组的有效组ID
unsigned short mode; //Permissions + SHM_DEST和SHM_LOCKED标志
unsignedshort seq; //序列号
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/*
* key:用来指定返回 MQ 的 ID
* msgflg:创建的标志,例如 IPC_CREAT
* return:成功返回队列 ID,失败返回 -1, 并设置 erron
*/
int msgget(key_t key, int msgflg);
注意:msgflg是权限标志位,可以设置为IPC_CREAT表示创建消息队列,如果存在就直接打开;也可以设置为IPC_CREAT | IPC_EXCL表示创建消息队列,如果key所命名的消息队列存在时,就返回错误。
在程序中若要使用消息队列,必须要能知道消息队列的key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性。
申请一块内存,创建一个新的消息队列(数据结构msqid_ds),将其初始化后加入到msgque向量表中的某个空位置处,返回标示符。或者在msgque向量表中找键值为key的消息队列。
如果函数调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.
msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。
使用 msgrcv 来从 msgqid 标识的 MQ 中读取一个消息放到 msgp 指定的内存(缓冲区)中,必须要有读消息队列的权限。
函数调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/*
* msqid:消息队列 ID
* cmd:控制命令,例如 IPC_RMID 删除命令
* buf:存储消息队列的相关信息的 buf
* return:成功根据不同的 cmd 有不同的返回值,失败返回 -1, 并设置 erron
*/
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
command是将要采取的动作,它可以取3个值,
一个进程A写入字符串到消息队列,另一个进程B从队列中取出数据。
发送消息的一方:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf {
long mtype;
char mtext[255];
};
int main(){
//1,创建一个消息队列,用key=123来唯一表示这个队列
int msg_id = msgget(123, IPC_CREAT | 0666);
if(msg_id != -1){
//2,初始化要发送的消息
struct msgbuf mybuf;
mybuf.mtype = 1;
strcpy(mybug.mtext, "I'm send process.\n");
//3,发送消息
if(msgsnd(msg_id, &mybuf, sizeof(mybuf.mtext), 0)){
printf("success.\n");
}
else
perror("msgsnd:");
}else{
perror("msgget:");
}
return 0;
}
接收消息的一方:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf {
long mtype;
char mtext[255];
};
int main(){
//1,获取消息队列
int msg_id = msgget(123, IPC_CREAT | 0666);
if(msg_id != -1){
struct msgbug mybuf;
//2,接收第一条消息,存到mybuf中
if(msgrcv(msg_id, &mybuf, sizeof(mybuf.mtext), 0, IPC_NOWAIT) != -1){
printf("read success: %s\n", mybuf.mtext);
//3,接收完消息,就删除这个消息队列
if(msgctl(msg_id, IPC_RMID, 0) != -1){
printf("delete msg success\n");
}else{
perror("msgctl:");
}
}
}
else{
perror("msgget:");
}
return 0;
}