八、进程间通信-消息队列
相关链接:消息队列
一、概述
1、什么是消息队列
消息队列是进程间通信的一种,它是由操作系统维护的以字节序列为基本单位的间接通信机制,遵循先进先出的原则,它提供了一个进程向另一个进程发送一个带类型的数据块的方法。
2、特点:
-
消息队列是进程或线程间通讯的其中一种方式。遵循先进先出的原则,保证了时间的顺序性。拥有该消息队列读权限的进程可以从消息队列读出数据,拥有该消息队列写权限的进程可以向消息队列发送数据。
-
消息作为节点一个一个地存放在消息队列里,可把消息队列比作信箱,消息比作依次顺序存放的信件。地址比作消息类型,内容为消息。
-
支持双向传输,可以使用消息类型区分不同的消息。
-
任何不同的进程都可以进行通讯,不局限于父子进程的通讯。
-
不足之处是消息的读写涉及数据拷贝,比较花费时间
消息队列有两套机制:system v 和posix 前者移植性比较好,后者使用比较简单还可配合select机制使用、信号和线程通知。
3、消息队列和有名管道的区别
-
消息队列对每个数据都有一个最大长度的限制。
-
消息队列也可以独立于发送和接收进程而存在,在进程终止时,消息队列及其内容并不会被删除
-
管道只能承载无格式字节流,消息队列提供有格式的字节流,可以减少了开发人员的工作量
-
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通过消息类型有选择地接收数据, 而不是像命名管道中那样,只能默认地接收。
-
消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息的类型接收。
4、通过如下命令可以查看系统当前的IPC对象,没有使用的情况下可能为空
二、消息队列的用法
1、定义一个唯一key(ftok)
key_t ftok(const char *pathname,int proj_id) //获取一个key :将一个路径名和项目id转换成一个System V IPC key
(1)参数:
- pathname:一个合法路径(路径必须存在)。
- proj_id:一个整数(不为0)。
当使用相同的 proj_id 值时,命名同一文件的所有路径名的结果值相同。
(2)返回值
- 成功:返回合法键值
- 失败: -1.
2、构造消息对象(msgget)
int msgget(key_t key,int msgflg)
(1)参数
- key:消息队列的键值。
- msgflg:
- IPC_CREAT: 如果消息队列不存在,则创建。
- mode:访问权限。
(2)返回值
-
成功:消息队列的ID
- 失败:-1
3、发送特定类型消息(msgsnd)
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
(1)参数
- msqid:消息队列ID。
- msgp:消息缓存区。
struct msgbuf
{
long mtype; //消息标识,必须大于0
char mtext[1]; //消息内容
}
(2)返回值
- 成功:0
- 失败:-1
4、接收特定类型消息(msgrcv)
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg)
(1)参数
-
msqid:消息队列ID.
-
msgp:消息缓存区。
-
msgs:消息正文的字节数。
-
msgtyp:要接收消息的标识。
-
msgflag:IPC_NOWAIT 非阻塞读取,MSG_NOERROR:截断消息。
(2)返回值
- 成功:0.
- 失败:-1
5、删除消息队列(msgctl)
(1)函数原型:
设置或获取消息队列的相关属性
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgqid,int cmd,struct maqid_ds *buf)
- msgqid:消息队列的ID
- cmd:
-
- IPC_STAT:获取消息队列的属性信息
-
- IPC_SET:设置消息队列的属性
-
- IPC_RMID:删除消息队列
- buf:相关结构体缓冲区
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
三、实例
使用消息队列实现两个进程的消息收发。
1、 数据发送
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define BUFFER_SIZE 512
struct message{
long msg_type;
char msg_text[BUFFER_SIZE];
};
void main(void)
{
int qid;
key_t key;
struct message msg;
/*根据不同的路径和关键字产生标准的key*/
if((key = ftok("/tmp",11)) == -1)
{
printf("ftok");
exit(1);
}
/*创建消息队列*/
if((qid = msgget(key,IPC_CREAT|0666)) == -1)
{
printf("msgget");
exit(1);
}
printf("Open queue %d\n",qid);
while(1)
{
printf("Enter some message to the queue:");
if((fgets(msg.msg_text,BUFFER_SIZE,stdin))==NULL)
{
puts("no message");
exit(1);
}
msg.msg_type = getpid();
/*添加消息到消息队列*/
if((msgsnd(qid, &msg, strlen(msg.msg_text),0)) < 0)
{
printf("message posted");
exit(1);
}
if(strncmp(msg.msg_text,"quit",4) == 0)
{
break;
}
}
exit(0);
}
2、数据接收
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define BUFFER_SIZE 512
struct message{
long msg_type;
char msg_text[BUFFER_SIZE]; //消息buffer
};
void main(void)
{
int qid;
key_t key;
struct message msg;
/*根据不同的路径和关键字产生标准的key*/
if((key = ftok("/tmp",11)) == -1)
{
printf("ftok");
exit(1);
}
/*创建消息队列*/
if((qid = msgget(key,IPC_CREAT|0666)) == -1)
{
printf("msgget");
exit(1);
}
printf("Open queue %d\n",qid);
do{
memset(msg.msg_text,0,sizeof(msg.msg_text));
/*接收消息到消息队列*/
if((msgrcv(qid, (void*)&msg, BUFFER_SIZE ,0,0)) < 0)
{
printf("msgrcv");
exit(1);
}
printf("The message form process %ld: %s",msg.msg_type,msg.msg_text);
}while(strncmp(msg.msg_text,"quit",4)); //接收到quit则退出数据接收
/*从系统内核中删除消息队列*/
if((msgctl(qid,IPC_RMID,NULL)) < 0)
{
printf("msgctl");
exit(1);
}
exit(0);
}
执行结果:
如果消息队列发送的数据长度大于接收设置的buffer大小,则消息队列会分两次进行接收。现在设置接收和发送的buffer都为10个字节,然后发送数据如下: