消息队列原理
首先消息队列(Message Queue)通过这个词首先能了解到的是队列这个先进先出数据结构,该方式可以从一个进程向另一个进程发送数据块,且每个数据块都被认为含有一个数据类型,所接受的进程可独立地接受不同数据类型的数据,可通过发送消息来避免管道的同步和阻塞问题。
Linux用宏MSGMAX、MSGMNB和MSGMNI分别设置了消息的最大长度和一个队列的最大长度和消息队列总数的上限。
消息队列和管道有很多相似之处,其可以通过队列的形式传递数据,MQ通过系统维护的链表来实现消息队列,在系统中每个MQ有唯一的ID来确定,每当有消息传来就会增加到MQ尾端,但其他进程取数据时不一定按照先进先出的顺序,也可按照消息的类型来取,这样就完成了进程间的通信。
消息队列API介绍
ftok()获取关键字
除消息队列外在另外两种进程间通信(信号量,共享内存)中都需要key_t类型的关键字ID值,用来唯一指定一个文件或文件夹路径,该ID值可以是我们自己定义的一个整型数值,更多的是通过调用ftok()
来获取ID。在Unix中该函数会将文件的索引节点号取出在前面加上子序号作为key_t的返回值。如果要保证key值不变的话要保证ftok函数指定的文件不被删除,否则不用ftok函数指定一个固定的key值。例如文件索引节点号为13579换算为十六进制则为0x350b,你所指定的子序号为21,换算为16进制为0x15,则最后的key_t值为0x15350b,查询文件的索引节点号的命令是:
msgget()创建或访问消息队列
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//调用成功返回一个非负数(消息队列标识符),失败返回-1
和前面介绍的信号量和共享内存一样都需要提供一个键来命名某个特定的消息队列。
- 第一个参数key为ftok()的返回的key_t类型值。
- 第二个参数msgflg为一个权限标志,表示消息队列的访问权限,msgflg可以与IPC_CREAT做或操作,不存在则创建,存在则返回已有的标识符,IPC_CREAT|IPC_EXCL不存在则创建,存在则返回出错。
msgsnd()添加到消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
//调用成功返回0,失败返回-1
- 第一个参数msgid为msgget()的返回消息队列ID。
- 第二个参数msg_ptr是一个指向准备发送消息的指针,消息的数据结构有一定的要求,该指针所指向的消息结构一档要是一个长整型成员变量开始的结构体,例如:
typedef struct s_msgbuf
{
long mtype;
char mtext[512]; //你想传送的数据
} t_msgbuf;
- 第三个参数msg_sz为msg_ptr指向的消息长度,不包括长整型消息类型的长度。
- 第四个参数msgflg为控制当前消息队满或消息到达系统范围的限制时长将发生的事,IPC_NOWAIT表示队满不等待,返回EAGAIN表示错误。
msgrcv()获取消息队列消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
//成功返回放到接收缓冲区的字节数,失败返回-1,并设置error
- 第一二三个参数与函数
msgsnd()
函数的作用一样。 - 第四个参数msgtype实现一种简单的优先级,若msgtype为0,则获取消息队列的第一个消息;若它的值大于0则获取具有相同消息类型的第一个信息;若它的值小于0则获取类型等于或小于msgtype的绝对值得第一个消息。
- 第五个参数msgflg用于控制当队列中没有相应类型的消息可接收将发生的事,msgflg=IPC_NOWAIT时队列没有可读消息不等待,返回ENOMSG;msgflg=MSG_NOERROR时消息超过msgsz时被截断;msgtype>0且msgflg=MSG_EXCEPT时接收类型不等于msgtype的第一条消息。
msgctl()控制消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int command, struct msgid_ds *buf);
//成功则返回0,失败则返回-1
- 第一个参数msgid为msgget函数所返回的消息队列ID。
- 第二个参数command为要采取的操作,可以取下面三个值:IPC_STAT将msgid_ds结构中的数据设置为消息队列的当前关联值;IPC_SET若有足够的权限,就把消息队列中的关联值设为msgid_ds结构中的值;IPC_RMID:删除消息队列。
- 第三个参数buf为一个指向msgid_ds结构的指针,指向消息队列模式和访问权限的结构,如下所示:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
示例代码
下述代码演示了进程写入消息到内核的消息队列:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define DELAY_TIME 3
typedef struct s_msgbuf //定义结构体
{
long mtype;
char mtext[512];
}msgbufs;
int main (int argc, char **argv)
{
key_t key;
int msg_id;
msgbufs msgbuf;
int i;
//创建消息队列
msg_id = msgget(ftok("/dev/zero", 21), 0666|IPC_CREAT);
if( msg_id < 0)
{
printf("creat mesqueue error: %s\n", strerror(errno));
return -2;
}
printf("key %d msgid %d \n", (int)key, msg_id);
for(i=0; i<3; i++)
{
//结构体的长整型数据设为消息队列键值
msgbuf.mtype = (int)key;
strcpy(msgbuf.mtext,"hello,simon");
//发送存放在结构体中的数据
if(msgsnd(msg_id, &msgbuf, sizeof(msgbuf.mtext), IPC_NOWAIT) < 0)
{
printf("send message failure: %s\n", strerror(errno));
break;
}
printf("Send message: %s\n", msgbuf.mtext);
sleep(1);
}
//删除消息队列,发送方先不要关闭,等接收完后再关闭
//msgctl(msg_id, IPC_RMID, NULL);
return 0;
}
运行结果:
panghu@Ubuntu-14:~$ ./msg_sender
key 352714760 ms_gid 524288
Send message: hello,simon
Send message: hello,simon
Send message: hello,simon
下列代码时从消息队列读出并打印消息:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define DELAY_TIME 3
typedef struct s_msgbuf //定义结构体
{
long mtype;
char mtext[512];
}msgbufs;
int main (int argc, char **argv)
{
key_t key;
int msg_id;
msgbufs msgbuf;
int i;
key = ftok("/dev/zero", 21);
//创建消息队列
msg_id = msgget(key, 0666|IPC_CREAT);
if( msg_id < 0)
{
printf("creat mesqueue error: %s\n", strerror(errno));
return -2;
}
printf("key %d ms_gid %d \n", (int)key, msg_id);
for(i=0; i<3; i++)
{
//将结构体置为0
memset(&msgbuf, 0, sizeof(msgbuf));
//从消息队列中能够读取数据到msgbuf
if(msgrcv(msg_id, &msgbuf, sizeof(msgbuf.mtext), (int)key, IPC_NOWAIT) < 0)
{
printf("send message failure: %s\n", strerror(errno));
break;
}
printf("Send message: %s\n", msgbuf.mtext);
sleep(1);
}
//删除消息队列
msgctl(msg_id, IPC_RMID, NULL);
return 0;
}
运行结果:
panghu@Ubuntu-14:~$ ./msg_recver
key 352714760 ms_gid 524288
Send message: hello,simon
Send message: hello,simon
Send message: hello,simon