目录
mq_open、mq_close 和 mq_unlink 函数
概述
消息队列可以看作一个消息链表。有足够写权限的进程可以往队列中放置任务,有足够读权限的线程可以从队列中取走消息。每个消息都是一个记录,由发送者赋予一个优先级。某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息到达。
对于 Posix 消息队列的读总是返回最高优先级的最早的消息。当往一个空队列放置一个消息时,Posix 消息队列允许产生一个信号或启动一个线程。
队列中每个消息有如下属性:
- 一个无符号整数优先级;
- 消息的数据部分长度(可以为0);
- 数据本身(如果长度大于0)。
mq_open、mq_close 和 mq_unlink 函数
mq_open() 函数用于创建一个消息队列或打开一个已存在的消息队列。
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag,
/* mode_t mode, struct mq_attr *attr */);
/* 若成功则为消息队列描述符,若出错则为-1 */
oflag 参数是 O_RDONLY、O_WRONLY 或 O_RDWR 之一,可能按位或上 O_CREAT、O_EXCL 或 O_NONBLOCK。参数标志详见文章【进程间通信】Posix IPC。
当实际操作是创建一个新队列时,mode 和 attr参数是需要的。attr 参数用于给新队列指定某些属性,如果它是空指针,就是用默认参数。
mq_close() 函数用于关闭已打开的消息队列。
#include <mqueue.h>
int mq_close(mqd_t mqdes);
/* 若成功则返回0,若出错则返回-1 */
调用进程只是关闭该描述符,但是消息队列并不从系统中删除。一个进程终止时,它的所有打开的消息队列都关闭,就像调用了 mq_close 一样。
mq_unlink() 函数用于从系统中删除创建出来的消息队列。
#include <mqueue.h>
int mq_unlink(const char *name);
每个消息队列有一个保存其当前打开着描述符的引用计数器,mq_open() 时计数器+1,mq_close() 时计数器-1。
当调用 mq_unlink 后会立即删除队列名,其它进程就没有办法再通过这个名称打开。之后会检查消计数器,计数器>0就先保留资源;计数器=0就立马释放资源。当最后一个 mq_close 调用后,计数器清零会立马触发资源释放。
mq_getattr 和 mq_setattr 函数
消息队列有四个属性,mq_getattr 返回所有这些属性,mq_setattr 设置其中某个参数。
#include <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *oattr);
/* 若成功则返回0,若出错则返回-1 */
mq_attr结构体包含属性如下:
字段 | 说明 | 默认值(Linux) | 可修改时机 |
---|---|---|---|
mq_flags | 控制队列行为:<br> - 0 :阻塞模式(默认)<br> - O_NONBLOCK :非阻塞模式 | 0 (阻塞) | 随时 |
mq_maxmsg | 队列容量(最大消息数) | 128 | 仅在创建时 |
mq_msgsize | 单个消息的最大字节数 | 1024 | 仅在创建时 |
mq_curmsgs | 当前队列中的消息数量(只读) | 0 (初始为空) | 不可修改 |
mq_send 和 mq_receive 函数
mq_send 用于往一个队列中发送一个消息,mq_receive 用于从队列中取走一个消息。每个消息都有一个优先级,是一个小于 MQ_PRIO_MAX 的无符号整数。Posix 要求这个上限至少为32。
mq_receive 总是返回所指定队列中最高优先级的最早消息,而且该优先级能随该消息的内容及其长度一同返回。
#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);
/* 若成功则为0,若出错则为-1 */
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *prio);
/* 若成功则为消息中字节数,若出错则为-1 */
mq_send 的 prio 参数是待发送消息的优先级,其值必须小于 MQ_PRIO_MAX。如果 mq_receive 的 priop 参数是一个非空指针,所返回消息的优先级就通过该指针存放。
如果不用使用不同优先级的消息,那就给 mq_send 指定值为0的优先级,给 mq_receive 指定一个空指针作为最后一个参数。
消息队列限制
队列的限制,都是在创建队列的时候建立的。
mq_mqxmsg 队列中的最大消息数;
mq_msgsize 给定消息的最大字节数。
消息队列的实现定义了另外两个限制:
- MQ_OPEN_MAX:一个进程能够同时拥有的打开着消息队列的最大数目(Posix要求至少为8);
- MQ_PRIO_MAX:任意消息的最大优先级值加1(Posix要求它至少为32);
两个常值定义在 <unistd.h> 头文件中。
消息队列使用示例
上面已经介绍了实现消息队列的所有基础操作,接下来是一个多进程使用消息队列进行通信的示例。
生产者进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <errno.h>
#include <unistd.h>
#define QUEUE_NAME "/myMessageQueue"
#define MAX_MSG_SIZE 1024
#define MSG_BUFFER_SIZE (MAX_MSG_SIZE + 10)
/* 属性打印函数 */
void printMqAttributes(mqd_t mqDes)
{
struct mq_attr attr;
if (mq_getattr(mqDes, &attr) == -1)
{
perror("mq_getattr");
return;
}
printf("消息队列属性:\n");
printf("最大消息数: %ld\n", attr.mq_maxmsg);
printf("最大消息大小: %ld\n", attr.mq_msgsize);
printf("当前消息数: %ld\n", attr.mq_curmsgs);
printf("\n");
}
int main()
{
mqd_t mq;
struct mq_attr attr;
/* 设置消息队列属性 */
attr.mq_flags = 0; // 0表示阻塞模式
attr.mq_maxmsg = 10; // 队列最多容纳10条消息
attr.mq_msgsize = MAX_MSG_SIZE; // 每条消息最大1024字节
attr.mq_curmsgs = 0; // 初始时队列中没有消息
mq = mq_open(QUEUE_NAME, O_CREAT | O_WRONLY, 0644, &attr);
if (mq == (mqd_t)-1)
{
perror("mq_open");
exit(EXIT_FAILURE);
}
printf("生产者: 消息队列创建/打开成功\n");
printMqAttributes(mq);
for (int i = 1; i <= 5; i++)
{
char msgText[50];
snprintf(msgText, sizeof(msgText), "生产者消息 %d (PID: %d)", i, getpid());
if (mq_send(mq, msgText, strlen(msgText) + 1, i) == -1)
{
perror("mq_send");
break;
}
printf("生产者: 发送消息 - %s (优先级: %d)\n", msgText, i);
sleep(1);
}
sleep(3);
if (mq_close(mq) == -1)
{
perror("mq_close");
}
printf("生产者: 关闭消息队列\n");
return 0;
}
消费者线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <errno.h>
#include <unistd.h>
#define QUEUE_NAME "/myMessageQueue"
#define MAX_MSG_SIZE 1024
#define MSG_BUFFER_SIZE (MAX_MSG_SIZE + 10)
/* 属性打印函数 */
void printMqAttributes(mqd_t mqDes)
{
struct mq_attr attr;
if (mq_getattr(mqDes, &attr) == -1)
{
perror("mq_getattr");
return;
}
printf("消息队列属性:\n");
printf("最大消息数: %ld\n", attr.mq_maxmsg);
printf("最大消息大小: %ld\n", attr.mq_msgsize);
printf("当前消息数: %ld\n", attr.mq_curmsgs);
printf("\n");
}
int main()
{
mqd_t mq;
char msgBuffer[MSG_BUFFER_SIZE];
unsigned int msgPriority;
mq = mq_open(QUEUE_NAME, O_RDONLY);
if (mq == (mqd_t)-1)
{
perror("mq_open");
exit(EXIT_FAILURE);
}
printf("消费者: 消息队列打开成功 (PID: %d)\n", getpid());
printMqAttributes(mq);
while (1)
{
ssize_t bytesRead = mq_receive(mq, msgBuffer, MSG_BUFFER_SIZE, &msgPriority);
if (bytesRead == -1)
{
if (errno == EAGAIN)
{
printf("消费者: 队列为空,等待消息...\n");
sleep(1);
continue;
}
perror("mq_receive");
break;
}
printf("消费者: 收到消息 - %s (优先级: %u)\n", msgBuffer, msgPriority);
if (strstr(msgBuffer, "END") != NULL)
{
printf("消费者: 收到结束信号\n");
break;
}
}
if (mq_close(mq) == -1)
{
perror("mq_close");
}
printf("消费者: 关闭消息队列\n");
if (mq_unlink(QUEUE_NAME) == -1)
{
perror("mq_unlink");
}
else
{
printf("消费者: 删除消息队列\n");
}
return 0;
}
运行结果:
异步通知 mq_notify 函数
mq_notify 是 POSIX 消息队列提供的一个异步通知机制,允许进程在消息到达空队列时获得通知,而不需要持续轮询队列。
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
当消息到达空队列时,系统会通知注册的进程,通知只会在队列从空变为非空时触发一次
通知方式:
- 信号通知:通过发送指定信号(SIGEV_SIGNAL)
- 线程创建:创建新线程执行回调函数(SIGEV_THREAD)
信号同时方式
struct sigevent sev;
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
if (mq_notify(mq, &sev) == -1) {
perror("mq_notify");
exit(EXIT_FAILURE);
}
线程回调方式
void msg_callback(union sigval sv) {
mqd_t *mqptr = (mqd_t *)sv.sival_ptr;
// 处理消息...
}
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = msg_callback;
sev.sigev_value.sival_ptr = &mq;
if (mq_notify(mq, &sev) == -1) {
perror("mq_notify");
exit(EXIT_FAILURE);
}