什么是消息队列
消息队列的概念
消息队列(Message,简称MQ):消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的,每个消息队列的总的字节数是有上限的,系统上消息队列的总数也有一个上限。
消息队列传递的是消息,消息即是我们需要在进程间传递的数据,消息队列采用链表来实现消息队列,该链表是由系统内核维护,系统中可能有很多的消息队列,每个消息队列用消息队列描述符(消息队列ID:qid)来区分,qid是唯一的,用来区分不同的消息队列。在进行进程间通信时,一个进程将消息加到消息队列尾端,另一个进程从消息队列中取消息(不一定以先进先出来取消息,也可以按照消息类型字段取消息),这样就实现了进程间的通信。
下面我画了一个简单的MQ模型,进程A向内核维护的消息队列中发消息,进程B从消息队列中取消息,从而实现了A和B的进程间通信。
消息队列用到的函数
1、msgget
函数原型
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
函数说明:该函数用来创建消息队列ID,成功返回一个非负整数,即该共享内存段的标识码,失败返回-1.
参数说明:第一个参数key是ftok()返回的key_t类型键值;
第二个参数msgflg是创建标志:IPC_CREAT,不存在则创建,存在则返回已有的mqid,IPC_CREAT|IPC_EXCL,不存在则创建,存在则返回出错。
2、msgsnd
函数原型
#include <sys/msg.h>
int msgsnd(int msqid ,const void *msgp, size_t msgsz, int msgflg);
函数说明:用来发送一个消息,必须要用写消息队列的权限。成功返回0,失败返回-1,并设置errno。
参数说明:第一个参数msgid是由msgget函数返回的消息队列ID;
第二个参数msgp是一个指针,它指向要发送的消息结构体类型的变量,消息结构在两方面收到制约。首先,它必须小于系统规定的上限值;其次,它必须以一个long int 长整数开始,接收者函数将利用这个长整数确定消息的类型,其参考类型定义形式如下:
typedf struct s_msgbuf
{
long mtype;
char mtext[512];
}t_msgbuf
第三个参数msgsz是要发送消息的长度;
第四个参数msgflg控制着当前消息队列满或到达系统上限时即将要发生的事情,设置为IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。
3、msgrcv
函数原型
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
函数说明:用来从一个消息队列接收消息,成功返回实际放到接收缓冲区里去的字符个数,失败返回-1,并设置errno。
参数说明:第一个参数msgid是由msgget函数返回的消息队列ID;
第二个参数msgp是一个指针,它指向准备接收的消息;
第三个参数msgsz是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型;
第四个参数msgtype是消息的类型,它可以实现接收优先级的简单形式;
msgtype=0返回队列第一条消息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
第五个参数msgflg控制着队列中没有相应类型的消息可供接收时将要发生的事;
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。
4、msgctl
函数原型
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数说明:该函数用于控制消息队列
参数说明:第一个参数msgid是由msgget函数返回的消息队列ID;
第二个参数cmd是要采取的操作,它可以采取下面的三个值:
IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值;
IPC_SET:如果进程有足够的权限,就把消息队列的当前关联值设置为msqid_ds结构中给出的值;
IPC_RMID:删除消息队列;
第三个参数:buf是一个结构指针,它指向存储消息队列的相关信息的buf;
消息队列的代码示例
了解了消息队列和相应的函数,对于消息队列的使用可以分为四个步骤:
- 创建和访问MQ
- 发送消息
- 接收消息
- 删除MQ
下面我们将写两个进程,其中进程msgqueue_sender往内核的消息队列里写入内容“Ping”,而进程msgqueue_recver则从消息队列里读出并打印该消息。
第一个进程msgqueue_sender
/*********************************************************************************
* Copyright: (C) 2020 makun<1394987689@qq.com>
* All rights reserved.
*
* Filename: shared_mem_write.c
* Description: This file
*
* Version: 1.0.0(2020年03月21日)
* Author: makun <1394987689@qq.com>
* ChangeLog: 1, Release initial version on "2020年03月21日 02时56分59秒"
*
********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define FTOK_PATH "/dev/zero"
#define FTOK_PROJID 0x22
typedef struct s_msgbuf
{
long mtype;
char mtext[512];
}t_msgbuf;
int main (int argc, char **argv)
{
key_t key;
int msgid;
t_msgbuf msgbuf;
int msgtype;
int i;
if( (key =ftok(FTOK_PATH, FTOK_PROJID)) <0)
{
printf("ftok get IPC token failure:%s\n",strerror(errno));
return -1;
}
msgid =msgget(key, IPC_CREAT |0666);
if( msgid < 0)
{
printf("shmget create shared memroy failure:%s\n",strerror(errno));
return -2;
}
msgtype= (int)key;
printf("key %d msgid %d msgypte %d\n", (int)key,msgid,msgtype);
for(i=0; i<4; i++)
{
msgbuf.mtype =msgtype;
strcpy(msgbuf.mtext, "Ping");
if( msgsnd(msgid,&msgbuf,sizeof(msgbuf.mtext),IPC_NOWAIT) <0)
{
printf("msgsnd() send message failure: %s\n", strerror(errno));
break;
}
printf("send message :%s\n",msgbuf.mtext);
sleep(1);
}
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
第二个进程msgqueue_rever
/*********************************************************************************
* Copyright: (C) 2020 makun<1394987689@qq.com>
* All rights reserved.
*
* Filename: shared_mem_write.c
* Description: This file
*
* Version: 1.0.0(2020年03月21日)
* Author: makun <1394987689@qq.com>
* ChangeLog: 1, Release initial version on "2020年03月21日 02时56分59秒"
*
********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define FTOK_PATH "/dev/zero"
#define FTOK_PROJID 0x22
typedef struct s_msgbuf
{
long mtype;
char mtext[512];
}t_msgbuf;
int main (int argc, char **argv)
{
key_t key;
int msgid;
t_msgbuf msgbuf;
int msgtype;
int i;
if( (key =ftok(FTOK_PATH, FTOK_PROJID)) <0)
{
printf("ftok get IPC token failure:%s\n",strerror(errno));
return -1;
}
msgid =msgget(key, IPC_CREAT |0666);
if( msgid < 0)
{
printf("shmget create shared memroy failure:%s\n",strerror(errno));
return -2;
}
msgtype= msgtype= (int)key;
printf("key %d msgid %d msgypte %d\n", (int)key,msgid,msgtype);
for(i=0; i<4; i++)
{
memset(&msgbuf, 0, sizeof(msgbuf));
if( msgrcv(msgid,&msgbuf,sizeof(msgbuf.mtext),msgtype,IPC_NOWAIT) <0)
{
printf("msgsnd() receive message failure: %s\n", strerror(errno));
break;
}
printf("receive message :%s\n",msgbuf.mtext);
sleep(1);
}
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
进程一给进程二发消息
进程二收到消息后: