Linux进程间通信四 Posix 消息队列简介与示例

目录

1. Posix 消息队列简介

2. API接口

2.1 创建或打开消息队列

2.2 发送消息

2.3 接收消息

2.4 获取、设置消息队列属性

2.5 关闭消息队列

2.6 删除消息队列

2.7 注册消息通知

3. 示例

3.1 消息生产者

3.2 消息消费者

3.3 编译运行

4. 通过文件系统操作消息队列

5. 参考文档


1. Posix 消息队列简介

Posix 消息队列是基于文件描述符的,因此可以使用诸如select、poll和epoll等IO多路复用方式,这应该是最大的优点,此外,它还是一个优先级队列,优先级最高的消息会先出队列。从我的开发经验来看,实际项目中并不推荐使用系统提供的消息队列,主要原因是限制太多,比如说Posix 消息队列,系统范围内能够创建的消息队列个数是优先的,此外,消息的大小和消息队列的容量也受限,还有一点是仅支持优先级,不支持FIFO,这一点SystemV消息队列是支持的。虽然不怎么使用,但是还是需要学习一下它的基本用法,这样后续需要自己设计的时候可以参考下。

Posix mq可以使用mq_open来创建或者获取,该函数返回消息队列描述符mqd_t,每个消息队列都有一个名字,以斜杠'/'开头,长度不超过NAME_MAX,且后面不能再有斜杠。不同的进程可以通过同一个名字来操作同一个消息队列。消息通过mq_sendmq_receive来接收和发送,当不需要消息队列的时候,使用mq_close关闭它,关闭不代表删除,消息队列具有系统生存期,可以使用mq_unlink来删除它。对了,Posix mq还提供一个异步接收消息的特性,使用mq_notify函数,该接口向消息队列注册或注销一个通知,希望有消息的时候能够通知它,通知方式有两种,一种是通过信号,一种是通过一个线程。这里限制比较多,首先,消息队列只能注册一个通知,其次,通知是一次性的,通知完如果还想继续通知需要重新注册,还有一点是如果消息被其它进程通过mq_receive取到了,通知不会发生。我感觉这是一个非常鸡肋的功能,O(∩_∩)O哈哈~

下面介绍下相关的接口和使用例子

2. API接口

2.1 创建或打开消息队列

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>


/**
* @brief 创建或获取消息队列
*
* @params name 消息队列关联的名字,斜杠开头,长度不超过NAME_MAX(e.g. 255)
* @params oflag 标志位,可以O_RDONLY| O_WRONLY| O_RDWR| O_CLOEXEC| O_CREAT| O_EXCL| O_NONBLOCK
* 这里如果指定了O_CREAT标志位,还要填写额外两个参数,mode和attr
*
* @params mode,参考open函数,通常填0即可
* @params attr,设置一些属性
*           struct mq_attr {
*               long mq_flags;       /* 不用填 */
*               long mq_maxmsg;      /* Max. # 消息队列中消息最大个数 */
*               long mq_msgsize;     /* Max. message size (bytes) 最大容量 */
*               long mq_curmsgs;     /* 不用填写 */
*           };
* @returns 成功返回描述符,失败返回-1
*/

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode,
                     struct mq_attr *attr);

# 需要加上编译选项-lrt
* Link with -lrt.

2.2 发送消息

#include <mqueue.h>

/**
* @brief 发送消息,如果队列满了并且没有设置NONBLOCK标志位会阻塞
*
* @params mqdes 消息队列描述符
* @params msg_ptr 要发送的消息buffer
* @params msg_len 消息大小
* @params msg_prio 消息优先级
*
* @returns 成功返回0,失败返回-1
**/

int mq_send(mqd_t mqdes, const char *msg_ptr,
                     size_t msg_len, unsigned int msg_prio);

#include <time.h>
#include <mqueue.h>

/**
* @brief 同上,多了一个时间参数,用于控制在队列满了的情况下,等待多长时间
*
* @params abs_timeout 时间
* @returns 成功返回0,失败返回-1
**/

int mq_timedsend(mqd_t mqdes, const char *msg_ptr,
                     size_t msg_len, unsigned int msg_prio,
                     const struct timespec *abs_timeout);

# 编辑要加上 -lrt选项
Link with -lrt.

2.3 接收消息

#include <mqueue.h>

/**
* @brief 接收消息,无消息时默认阻塞,可以设置不阻塞标志位更改
*
* @params mqdes 消息队列描述符
* @params msg_ptr 消息缓存buffer
* @params msg_len 消息缓存大小,必须大于mq_msgsize,这个值可以通过mq_getattr接口获得
* @params msg_prio 如果设置了则返回消息的优先级

* @returns 成功返回消息大小,失败返回-1
**/

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
                  size_t msg_len, unsigned int *msg_prio);

#include <time.h>
#include <mqueue.h>

/**
* @brief 功能同上,只不过多了一个阻塞时间
**/

ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                 size_t msg_len, unsigned int *msg_prio,
                 const struct timespec *abs_timeout);

2.4 获取、设置消息队列属性

#include <mqueue.h>

/**
* @brief 获取消息队列属性
*
* @params mqdes 消息队列描述符
* @params attr 属性buffer

           struct mq_attr {
               long mq_flags;       /* Flags: 0 or O_NONBLOCK */
               long mq_maxmsg;      /* Max. 最大消息个数 */
               long mq_msgsize;     /* Max. 消息队列最大容量 */
               long mq_curmsgs;     /* 当前队列消息数 */
           };
* @returns 成功返回0,失败返回-1
**/

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);

/**
* @brief 设置消息队列属性
*
* @params mqdes 消息队列描述符
* @params attr 属性buffer, 这里实际上只能设置attr里面的mq_flags标志位
* @returns 成功返回0,失败返回-1
**/

int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr,
                        struct mq_attr *oldattr);

# 编译链接选项 -lrt
Link with -lrt.

2.5 关闭消息队列

#include <mqueue.h>

/**
* @brief 关闭消息队列,并不实际删除消息队列
*
* @params mqdes 消息队列描述符
* @returns 成功返回0,失败返回-1
**/

int mq_close(mqd_t mqdes);

2.6 删除消息队列

#include <mqueue.h>
/**
* @brief 将消息队列从系统中删除,实际的销毁要等到所有进程关闭消息队列描述符才发生
*
* @params name 消息队列名字
* @returns 成功返回0,失败返回-1
**/

int mq_unlink(const char *name);

2.7 注册消息通知

#include <mqueue.h>

/**
* @brief 注册或注销一个消息通知,当收到消息的时候根据指定的方式进行回调,可以是信号也可以执行一个线程。仅执行一次,需要反复通知则每次都要重新注册,最多允许一个进程注册。只有消息队列从空变为非空并且没有其它进程等待消息的时候,才触发通知。
*
* @params mqdes 消息队列名字
* @params sevp, 为空表示注销,非空指定回调方式,或是信号或者是线程
* @returns 成功返回0,失败返回-1
**/

int mq_notify(mqd_t mqdes, const struct sigevent *sevp);

3. 示例

3.1 消息生产者

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define MQ_NAME "/mq_0"

struct smessage{
    char name[128];
    char content[1024];
};

int main(int argc, char** argv) 
{
    int ret = 0;
    
    if (argc < 4)
    {
        printf("Usage: ./mq_write name message prio\n");
        return 0;
    }

    mqd_t mq_fd;
    // 打开或创建消息队列,如果指定C_CREAT标志位则需要提供额外两个参数
    mq_fd = mq_open(MQ_NAME, O_RDWR|O_CREAT, 0, NULL);
    if (mq_fd == (mqd_t)-1)
    {
        perror(__FUNCTION__);
        return -1;
    }
    struct smessage msg;
    memset(&msg, 0, sizeof(msg));
    strncpy(msg.name, argv[1], strlen(argv[1]));
    strncpy(msg.content, argv[2], strlen(argv[2]));
    unsigned int prio = (unsigned int)atoi(argv[3]);

    // 发送消息
    ret = mq_send(mq_fd, (const char *)&msg, sizeof(msg), prio);
    if (ret == -1)
    {
        perror("mq_send error");
        return ret;
    }
    printf("mq_send succ\n");

    return 0;
}

3.2 消息消费者

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

// 消息队列名字
#define MQ_NAME "/mq_0"

struct smessage{
    char name[128];
    char content[1024];
};

int main() 
{
    mqd_t mq_fd;
    // 打开或创建消息队列,如果指定O_CREAT,则需要填充额外两个参数
    mq_fd = mq_open(MQ_NAME, O_RDWR|O_CREAT, 0, NULL);
    if (mq_fd == (mqd_t)-1)
    {
        perror(__FUNCTION__);
        return -1;
    }

    // 获取消息队列属性,读取消息的时候要用到
    struct mq_attr attr;
    if (mq_getattr(mq_fd, &attr) == -1)
    {
        perror("mq_getattr");
        return -1;
    }
    printf("mq_msgsize:%ld\n", attr.mq_msgsize);

    // 分配消息缓存
    char *buffer = malloc(attr.mq_msgsize);
    if (buffer == NULL)
    {
        printf("malloc error\n");
        return -1;
    }
    memset(buffer, 0, attr.mq_msgsize);
            
    struct smessage *msg;
    unsigned int prio = 0;

    // 读取消息
    if (mq_receive(mq_fd, buffer,attr.mq_msgsize, &prio) != -1)
    {
        msg = (struct smessage*)buffer;
        printf("recv msg, name:%s, content:%s, prio:%lu\n", msg->name, msg->content, prio);
    }
    else
    {
        perror("mq_receive error");
    }

    // 释放buffer
    free(buffer);

    // 关闭消息队列
    mq_close(mq_fd);

    return 0;
}

3.3 编译运行

# Makefile
default:
	gcc -o mq_read mq_read.c -lrt
	gcc -o mq_write mq_write.c -lrt
clean:
	rm -rf mq_read mq_write

生产三条优先级不同的消息,可以看到读取的时候是按照优先级来读取的。 

 

4. 通过文件系统操作消息队列

 # mkdir /dev/mqueue
 # mount -t mqueue none /dev/mqueue

 

5. 参考文档

5.1. https://www.man7.org/linux/man-pages/man7/mq_overview.7.html

================================================================================================

Linux应用程序、内核、驱动、后台开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章