Linux提供了两种消息队列机制,POSIX Messages以及System V Message Queues。
POSIX Messages
打开或创建消息队列:mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr)
参数名 | 说明 |
---|---|
name | 消息队列名,任意取。必须从跟目录开始,且只能包含一个‘/’ ,例如:/example ;错误:/example/anything |
oflag | 消息队列的操作方式:O_RDONLY 只读,O_WRONLY 只写,O_RDWR 可读可写,O_CREAT 创建。更多请参考man 3 mq_open |
mode | 消息队列权限 |
attr | 消息队列属性,下面mq_getattr( )会讲到 |
返回值 | 说明 |
---|---|
非负整数 | 成功 |
-1 | 失败 |
关闭消息队列:int mq_close(mqd_t mqdes) |
参数 | 说明 |
---|---|
mqdes | 消息队列描述符,mq_open() 的返回值 |
返回值 | |
---|---|
0 | 成功关闭 |
-1 | 关闭失败,原因可能为消息队列不存在,或者关闭未打开的消息队列,具体看errno 的值 |
删除消息队列:int mq_unlink(const char *name) | |
参数 | 说明 |
– | :– |
name | 消息队列的标识名,与mq_open() 第一个参数一样 |
返回值 | 说明 |
---|---|
0 | 删除成功 |
-1 | 删除失败,并设置errno ,通过errno 可以知道失败原因 |
发送消息:
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
注意:打开消息队列的时候,可以选择阻塞或非阻塞方式。在消息队列为满的情况下,以阻塞的方式往消息队列写入数据会等待,直到消息队列有空余位置才往里写;非阻塞方式写会报错立即返回,设置errno
为EAGAIN
。
参数 | 说明 |
---|---|
mqdes | 同上 |
msg_ptr | 需要发送的消息。不一定是字节流,可以定义你需要的数据结构,发送的时候强制转换为(const char *) |
msg_len | 发送的消息大小。内核对其大小边界作了限定(跟内核版本有关),详细请man mq_overiew(7) |
msg_prio | 消息优先级,值越大优先级越高。 |
abs_timeout | 阻塞的超时时间。根据前面的描述,阻塞调用mq_send() 会一直等待直到队列有空间为止,但是mq_timedsend() 可以设置等待时间,超时就返回,不会无限等待。 |
返回值 | 说明 |
---|---|
0 | 发送成功 |
-1 | 发送失败,并设置errno ,通过errno 可以知道失败原因 |
获取消息: | |
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio); | |
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout); | |
参数 | 说明 |
– | :– |
mqdes | 同上 |
msg_ptr | 从队列取出的消息。不一定是字节流,可以强制转换为你需要的数据结构 |
msg_len | 需要接受的消息长度。这个值必须大于或等于消息队列大小,可以通过msg_getattr() 获取消息队列大小` |
msg_prio | 消息优先级,值越大优先级越高。 |
abs_timeout | 阻塞调用mq_receive() 会一直等待直到队列有消息为止,mq_timedreceive() 可以设置等待时间,超时就返回。 |
返回值 | 说明 |
---|---|
正整数 | 接收到的消息长度。 |
-1 | 接收失败,并设置errno ,通过errno 可以知道失败原因 |
获取队列属性:int mq_getattr(mqd_t mqdes, const struct mq_attr *attr); |
//属性数据结构
struct mq_attr
{
long mq_flags; /* Flags: 0 or O_NONBLOCK */
long mq_maxmsg; /* Max. # of messages on queue */ //消息最大条数
long mq_msgsize; /* Max. message size (bytes) */ //单条消息大小
long mq_curmsgs; /* # of messages currently in queue */ //队列中当前消息条数
};
参数 | 说明 |
---|---|
mqdes | 同上 |
attr | 从内核中获取当前消息队列的属性 |
设置队列属性:int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr); | |
注意:只能修改mq_attr中mq_flags成员:0 阻塞或者O_NONBLOCK 非阻塞。 | |
参数 | 说明 |
– | :– |
mqdes | 同上 |
newattr | 目标属性。要设置的新的属性 |
oldattr | 原有属性。设置前先通过mq_getattr() 获取 |
POSIX测试代码
接收进程:mq_read.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define MQ_PATH "/test"
typedef struct{
int year;
char name[16];
}Student;
int main()
{
mqd_t mqfd = mq_open(MQ_PATH, O_RDONLY, 0666, NULL);
if(mqfd == (mqd_t) -1)
{
perror("mq_open");
if(errno != EEXIST)
{
return -1;
}
}
struct mq_attr attr;
memset(&attr, 0x00, sizeof(attr));
mq_getattr(mqfd, &attr);//默认消息条数为10,单条最大8K
int rc = -1;
unsigned int prio;
Student *stu = NULL;
char *msgbuf = (char *)malloc(attr.mq_msgsize);
while(1)
{
rc = mq_receive(mqfd, msgbuf, attr.mq_msgsize, &prio);
if(rc > 0)
{
stu = (Student *)msgbuf;
printf("Recv: prio = %d, year = %d, name = %s\n", prio, stu->year, stu->name);
if(strcmp("quit", stu->name) == 0)
break;
}
}
mq_unlink(MQ_PATH);
return 0;
}
发送进程0:mq_send0.c,优先级为0的进程
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#define MQ_PATH "/test"
typedef struct{
int year;
char name[16];
}Student;
int main()
{
mqd_t mqfd = mq_open(MQ_PATH, O_WRONLY|O_CREAT, 0666, NULL);
if(mqfd == (mqd_t) -1)
{
perror("mq_open");
if(errno != EEXIST)
{
return -1;
}
else
{
mq_unlink(MQ_PATH);
mqfd = mq_open(MQ_PATH, O_WRONLY|O_CREAT, 0666, NULL);
}
}
int rc = -1, count = 0;
unsigned int prio;
Student stu = {18, "XiaoMing"};
while(1)
{
rc = mq_send(mqfd, (const char *)&stu, sizeof(stu), 0);
if(rc == 0)
{
count++;
if(count == 10)
{
memset(stu.name, 0x00, sizeof(stu.name));
strcpy(stu.name, "quit");
mq_send(mqfd, (const char *)&stu, sizeof(stu), 0);//消息优先级为0
break;
}
printf("send0: year = %d, name = %s\n", stu.year, stu.name);
}
sleep(2);
}
mq_close(mqfd);
return 0;
}
发送进程1:mq_send1.c,优先级为1的进程
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#define MQ_PATH "/test"
typedef struct{
int year;
char name[16];
}Student;
int main()
{
mqd_t mqfd = mq_open(MQ_PATH, O_WRONLY, 0666, NULL);
if(mqfd == (mqd_t) -1)
{
perror("mq_open");
if(errno != EEXIST)
{
return -1;
}
}
int rc = -1;
unsigned int prio;
Student stu;
stu.year = 18;
strcpy(stu.name, "XiaoMing");
int count = 0;
while(1)
{
rc = mq_send(mqfd, (const char *)&stu, sizeof(stu), 1);
if(rc == 0)
{
count++;
if(count == 10)
{
memset(stu.name, 0x00, sizeof(stu.name));
strcpy(stu.name, "quit");
mq_send(mqfd, (const char *)&stu, sizeof(stu), 0);
break;
}
printf("send1: year = %d, name = %s\n", stu.year, stu.name);
}
sleep(2);
}
mq_close(mqfd);
return 0;
}
运行顺序:先运行send0,因为要创建消息队列,注意下面两种情况:
1.等send0发送了3条之后,再启动send1,sen1发了2条之后,启动read进程,观察发现read先收到send1发送的消息,验证了优先级的问题(这个应该不难理解);
2.先不启动read进程,在send0和send1加起来共发送10条消息时,发现两个send进程都阻塞不发送了,此时启动read进程,两个send进程会继续发送消息。
System V消息队列
Linux中定义了通用的消息数据结构:
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
消息的数据结构可以由用户自己定义,但是数据结构的第一个成员必须为long整形的消息类型。
操作流程:
1.生成key:key_t ftok(const char *pathname, int proj_id);
参数 | 说明 |
---|---|
pathname | 路径,此路径为真实存在,不存在的话会创建 |
proj_id | 消息队列对象标识,任意大于0的数 |
2.创建消息队列:int msgget(key_t key, int msgflg);
成功返回msgid
,失败返回-1
;
参数 | 说明 |
---|---|
key | 由ftok() 生成 |
msgflg | 指定消息操作模式以及权限。格式:`IPC_CREAT |
3.接收消息:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); | |
参数 | 说明 |
– | :– |
msqid | 消息队列标识 |
msgp | 接收到的消息 |
msgsz | 要接收的消息长度 |
msgtyp | 消息类型。如果消息队列里面存在多种类型消息,接收进程只接收指定类型消息。 |
msgflg | 消息队列的操作模式。 |
返回值 | 说明 |
---|---|
len | 成功接收len字节数据 |
-1 | 失败 |
4.发送消息:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); | |
参数 | 说明 |
– | :– |
msqid | 同上 |
msgp | 需要发送的消息,数据结构可以自定义,但结构体第一个成员必须是long int 的消息类型,mtype 。 |
msgsz | 要发送的消息长度。 |
msgflg | 消息队列的操作模式。 |
返回值 | 说明 |
---|---|
0 | 发送成功 |
-1 | 失败 |
5.消息队列销毁:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数 | 说明 |
---|---|
msqid | 同上 |
cmd | 消息队列操作命令。删除使用:IPC_RMID ,更多类型请man msgctl |
buf | 删除使用:NULL |
返回值 | 说明 |
---|---|
0 | 删除成功 |
-1 | 失败 |
删除消息队列还可以在shell命令行下操作: | |
先通过ipcs 命令查看消息队列的key或msgid | |
然后命令行下输入: | |
ipcrm --queue-key 0x31010164 | |
或者 | |
ipcrm --queue-id 98304 。 |
System V测试代码
这里创建三个接收进程,分别为msg_rcv.c,msg_rcv1.c,msg_rcv2.c,一个发送进程msg_send.c,以及头文件msgq.h,代码如下:
msgq.h
#ifndef __MSGQ_H__
#define __MSGQ_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <error.h>
#define MSG_PATH "./msg.txt"
typedef struct student_t{
char name[16];
int year;
}Student;
typedef struct message_t{
long mtype;
Student Info;
}Message;
typedef enum Info_e{
STUDENT_INFO = 1,
TEACHER_INFO,
QUIT,
}Info_type;
#endif
msg_rcv.c
#include "msgq.h"
int main(void)
{
key_t key = ftok(MSG_PATH, '1');
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, O_RDONLY);
if(msgid < 0)
{
perror("msgget");
return -1;
}
int rc = -1, count = 0;
Message msg;
while(1)
{
memset(&msg, 0x00, sizeof(Message));
if((rc = msgrcv(msgid, &msg, sizeof(Student), STUDENT_INFO, 0)) > 0)//接收学生类型消息
{
printf("rcv[%d]--->type[%d], name[%s], year[%d]\n", count++, STUDENT_INFO, msg.Info.name, msg.Info.year);
if(strcmp("quit", msg.Info.name) == 0)//接收到quit消息,退出。这里不删除消息队列,有专门的进程删除它
{
break;
}
}
}
return 0;
}
msg_rcv1.c
#include "msgq.h"
int main(void)
{
key_t key = ftok(MSG_PATH, '1');
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, O_RDONLY);
if(msgid < 0)
{
perror("msgget");
return -1;
}
int rc = -1, count = 0;
Message msg;
while(1)
{
memset(&msg, 0x00, sizeof(Message));
if((rc = msgrcv(msgid, &msg, sizeof(Student), TEACHER_INFO, 0)) > 0)//接收老师类型消息
{
printf("rcv[%d]--->type[%d], name[%s], year[%d]\n", count++, TEACHER_INFO, msg.Info.name, msg.Info.year);
if(strcmp("quit", msg.Info.name) == 0)//接收到quit消息,退出。这里不删除消息队列,有专门的进程删除它
{
break;
}
}
}
return 0;
}
msg_rcv2.c
#include "msgq.h"
int main(void)
{
key_t key = ftok(MSG_PATH, '1');
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, O_RDONLY);
if(msgid < 0)
{
perror("msgget");
return -1;
}
int rc = -1, count = 0;
Message msg;
while(1)
{
memset(&msg, 0x00, sizeof(Message));
if((rc = msgrcv(msgid, &msg, sizeof(Student), QUIT, 0)) > 0)//接收退出类型消息
{
printf("rcv[%d]--->type[%d], name[%s], year[%d]\n", count++, STUDENT_INFO, msg.Info.name, msg.Info.year);
msgctl(msgid, IPC_RMID, NULL);//删除消息队列
break;
}
}
return 0;
}
msg_send.c
#include "msgq.h"
int main(void)
{
key_t key = ftok(MSG_PATH, '1');
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, IPC_CREAT|O_WRONLY|0666);
if(msgid < 0)
{
perror("msgget");
return -1;
}
int rc = -1, count = 0;
Message msg;
msg.mtype = STUDENT_INFO;
msg.Info.year = 18;
strcpy(msg.Info.name, "Xiaoming");
while(1)
{
count++;
if(count % 2 != 0)
{
msg.mtype = TEACHER_INFO;
rc = msgsnd(msgid, &msg, sizeof(Student), 0);
}
else
{
msg.mtype = STUDENT_INFO;
rc = msgsnd(msgid, &msg, sizeof(Student), 0);
}
printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, msg.Info.name, msg.Info.year);
if(count == 10)
{
memset(msg.Info.name, 0x00, sizeof(msg.Info.name));
strcpy(msg.Info.name, "quit");
msg.mtype = STUDENT_INFO;
rc = msgsnd(msgid, &msg, sizeof(Student), 0);
printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, "quit", msg.Info.year);
sleep(1);
msg.mtype = TEACHER_INFO;
rc = msgsnd(msgid, &msg, sizeof(Student), 0);
printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, "quit", msg.Info.year);
sleep(1);
msg.mtype = QUIT;
rc = msgsnd(msgid, &msg, sizeof(Student), 0);
printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, "quit", msg.Info.year);
break;
}
sleep(2);
}
return 0;
}
简易Makefile
all: msg_rcv msg_rcv1 msg_rcv2 msg_send
.PHONY = all
msg_rcv:msg_rcv.o
cc -o msg_rcv msg_rcv.o
msg_rcv1:msg_rcv1.o
cc -o msg_rcv1 msg_rcv1.o
msg_rcv2:msg_rcv2.o
cc -o msg_rcv2 msg_rcv2.o
msg_send:msg_send.o
cc -o msg_send msg_send.o
.PHONY = clean
clean:
@rm -rf msg_rcv msg_rcv1 msg_rcv2 msg_send *.o
现象如下图:
接收学生消息进程
接收老师消息进程
接收退出消息进程
It’s over.