消息队列方式
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。
与命名管道一样,每个数据块都有一个最大长度的限制。Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。可以使用 ipcs -q
查看系统当前使用的消息队列(MQ)。
一、使用
1、 头文件
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
2、使用步骤及对应的函数原型
-
创建和访问MQ: int msgget(key_t key , int msgflg);
Key:返回MQ的ID
msgflg:创建的标志,如IPC_CREAT
返回值: 成功返回队列ID,失败返回-1并设置errno -
发送消息:int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg)
Msgid:消息队列ID
msgp:指向msgbuf的指针,用来指定要发送的消息
msgsz:要发送消息的长度(不是发送的数据结构体的长度)
msgflg:创建标记,如果指定为IPC_NOWAIT,失败会立即返回,成功返回0,
返回值失败返回-1,并设置errno -
接收消息:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
Msgid:消息队列ID
msgp:指向msgbuf的指针,用来接收消息
msgsz:要发送消息的长度,msgtyp为接收消息的方式
msgflg:创建标记,如果指定为IPC_NOWAIT,失败会立即返回
返回值:成功返回读取消息的字节数,失败返回-1,并设置errno
msgtyp的取值
(1)Msgtyp=0:读取队列第一条消息
(2)Msgtyp>0:读取队列中类型为msgtyp的第一条消息,除非在msgflg中指定了MSG_EXCEPT,否则将读取类型不等于msgtyp的队列中的第一条消息
(3)Msgtyp<0:读取队列中类型小于或等于msgtype绝对值的第一条消息 -
删除MQ:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Msqid:消息队列ID
cmd:控制命令,如IPC_RMID删除命令
buf:存储消息队列的相关信息的buf
返回值:成功则根据不同的cmd返回对应值,失败返回-1并设置errno
3、基本原理
二、演示程序
1、Test4-1-1向test4-1-2单向传递数据
//test4-1-1
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MSGLEN 64
struct msgbuf
{
long mtype;
char mtext[MSGLEN];
};
int main()
{
//创建一个消息队列,用key=445来唯一标识这个队列
int msg_id=msgget(445,IPC_CREAT | 0666);
if(msg_id==-1)
{
perror("Create MQ fail!")
exit(-1);
}
//初始化要发送的消息
struct msgbuf mybuf;
mybuf.mtype=1;
strcpy(mybuf.mtext,"I am test4-1-1! I am sending a message to you!\n");
//发送
if(msgsnd(msg_id,&mybuf,sizeof(mybuf.mtext),0)==-1)
{
perror("Send fail!")
exit(-1);
}
printf("Send success!\n");
printf("Send : %s\n\n",mybuf.mtext);
return 0;
}
//test4-1-2
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MSGLEN 64
struct msgbuf
{
long mtype;
char mtext[MSGLEN];
};
int main()
{
//创建一个消息队列,用key=445来唯一标识这个队列
int msg_id=msgget(445,IPC_CREAT | 0666);
if(msg_id==-1)
{
perror("Create MQ fail!")
exit(-1);
}
struct msgbuf mybuf;
//接收消息
if(msgrcv(msg_id,&mybuf,sizeof(mybuf.mtext),0,IPC_NOWAIT)==-1)
{
perror("Recv fail!")
exit(-1);
}
printf("Recv success!\n");
printf("Recv : %s\n\n",mybuf.mtext);
//删除消息队列
if(msgctl(msg_id,IPC_RMID,0)==-1)
{
perror("Delete fail!");
exit(-1);
}
printf("Delete success!\n");
return 0;
}
2、Test4-2-1与test4-2-2双向传递数据
通过控制recv函数的第四个参数即可实现双向传递数据
//test4-2-1
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MSGLEN 64
struct msgbuf
{
long mtype;
char mtext[MSGLEN];
};
int main()
{
pid_t pid;
pid=fork();
if(pid<0|pid>0)
return 1;
//创建一个消息队列,用key=445来唯一标识这个队列
int msg_id1=msgget(445,IPC_CREAT | 0666);
if(msg_id1==-1)
{
perror("Create MQ1 fail!");
exit(-1);
}
int msg_id2=msgget(446,IPC_CREAT | 0666);
if(msg_id2==-1)
{
perror("Create MQ2 fail!");
exit(-1);
}
//初始化要发送的消息
struct msgbuf sendBuf;
sendBuf.mtype=1;
strcpy(sendBuf.mtext,"I am test4-2-1! I am sending a message to you!\n");
//发送
if(msgsnd(msg_id1,&sendBuf,sizeof(sendBuf.mtext),0)==-1)
{
perror("[test4-2-1]Send fail!");
exit(-1);
}
printf("[test4-2-1]Send success!\n");
printf("[test4-2-1]Send : %s\n\n",sendBuf.mtext);
sleep(2);
struct msgbuf recvBuf;
memset(recvBuf.mtext,0,MSGLEN);
//接收消息
if(msgrcv(msg_id2,&recvBuf,sizeof(recvBuf.mtext),0,IPC_NOWAIT)==-1)
{
perror("[test4-2-1]Recv fail!");
exit(-1);
}
printf("[test4-2-1]Recv success!\n");
printf("[test4-2-1]Recv : %s\n\n",recvBuf.mtext);
//删除消息队列
if(msgctl(msg_id2,IPC_RMID,0)==-1)
{
perror("Delete fail!");
exit(-1);
}
printf("Delete success!\n");
return 0;
}
//test4-2-2
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MSGLEN 64
struct msgbuf
{
long mtype;
char mtext[MSGLEN];
};
int main()
{
pid_t pid;
pid=fork();
if(pid<0|pid>0)
return 1;
//创建一个消息队列,用key=445来唯一标识这个队列
int msg_id1=msgget(445,IPC_CREAT | 0666);
if(msg_id1==-1)
{
perror("Create MQ1 fail!");
exit(-1);
}
int msg_id2=msgget(446,IPC_CREAT | 0666);
if(msg_id2==-1)
{
perror("Create MQ2 fail!");
exit(-1);
}
struct msgbuf recvBuf;
recvBuf.mtype=1;
memset(recvBuf.mtext,0,MSGLEN);
//
//接收消息
if(msgrcv(msg_id1,&recvBuf,sizeof(recvBuf.mtext),0,IPC_NOWAIT)==-1)
{
perror("[test4-2-2]Recv fail!");
exit(-1);
}
printf("[test4-2-2]Recv success!\n");
printf("[test4-2-2]Recv : %s\n\n",recvBuf.mtext);
struct msgbuf sendBuf;
sendBuf.mtype=1;
strcpy(sendBuf.mtext,"I am test4-2-2! I am sending a message to you!\n");
//发送
if(msgsnd(msg_id2,&sendBuf,sizeof(sendBuf.mtext),0)==-1)
{
perror("[test4-2-2]Send fail!");
exit(-1);
}
printf("[test4-2-2]Send success!\n");
printf("[test4-2-2]Send : %s\n\n",sendBuf.mtext);
//删除消息队列
if(msgctl(msg_id1,IPC_RMID,0)==-1)
{
perror("Delete fail!");
exit(-1);
}
printf("Delete success!\n");
return 0;
}
运行结果:
三、补充
消息队列方式对准备发送的数据的数据结构有一定的要求,msgsnd函数第二个参数指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
1. struct my_message
2. {
3. long int message_type;
4. /* The data you wish to transfer*/
5. };
消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。
在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。其次,消息队列可以通过控制type实现双向通信