【进程间通信】Posix 消息队列

代码星辉·七月创作之星挑战赛 10w+人浏览 276人参与

目录

概述

mq_open、mq_close 和 mq_unlink 函数

mq_getattr 和 mq_setattr 函数

mq_send 和 mq_receive 函数

消息队列限制

消息队列使用示例

生产者进程

消费者线程

异步通知 mq_notify 函数

信号同时方式

线程回调方式 


概述

消息队列可以看作一个消息链表。有足够写权限的进程可以往队列中放置任务,有足够读权限的线程可以从队列中取走消息。每个消息都是一个记录,由发送者赋予一个优先级。某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息到达

对于 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);

当消息到达空队列时,系统会通知注册的进程,通知只会在队列从空变为非空时触发一次 

通知方式

  1. 信号通知:通过发送指定信号(SIGEV_SIGNAL)
  2. 线程创建:创建新线程执行回调函数(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);
}
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值