[Linux]POSIX 消息队列使用

一、消息队列

消息队列一般简称为 MQ (Messges Queue),是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,是在消息的传输过程中保存消息的容器。消息队列本质上是一个队列,而队列中存放的是一个个消息。

队列是一个数据结构,具有先进先出的特点。而消息队列就是将消息放到队列里,用队列做存储消息的介质。消息的发送放称为生产者,消息的接收方称为消费者。

消息队列由 Broker(消息服务器,核心部分)、Producer(消息生产者)、Consumer(消息消费者)、Topic(主题)、Queue(队列)和Message(消息体)组成。

二、POSIX消息队列

Linux中有两种消息队列的实现一种是POSIX消息队列,另一种是System V消息队列。

Posix消息队列与System V消息队列的区别如下:

(1) 对Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读则可以返回任意指定优先级的消息。

(2)当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程,System V消息队列则不提供类似的机制。

1.环境准备

首先需要内核支持POSIX消息队列,即在编译内核时添加编译选项:

CONFIG_POSIX_MQUEUE =y

其次,需要创建消息队列节点并将文件系统挂载至该节点

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

2.POSIX消息队列使用

POSIX消息队列的基本函数都在mqueue中实现,需要包含头文件<mqueue.h>

2.1 创建与关闭消息队列

POSIX消息队列的创建,关闭和删除用到以下三个函数接口:

#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag, /* mode_t mode, struct mq_attr *attr */);
                       //成功返回消息队列描述符,失败返回-1
mqd_t mq_close(mqd_t mqdes);
mqd_t mq_unlink(const char *name);
		//成功返回0,失败返回-1

mq_open用于打开或创建一个消息队列。

1.name:表示消息队列的名字,它符合POSIX IPC的名字规则。
2.oflag:表示打开的方式,和open函数的类似。有必须的选项:O_RDONLY,O_WRONLY,O_RDWR,还有可选的选项:O_NONBLOCK,O_CREAT,O_EXCL。
3.mode:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,才需要提供该参数。表示默认访问权限。可以参考open。
4.attr:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才需要。该参数用于给新队列设定某些属性,如果是空指针,那么就采用默认属性。

mq_open返回值是mqd_t类型的值,被称为消息队列描述符。本质上是一个int。

注意:消息队列创建的名称必须以"/“开头且只能包含这一个”/“。使用mq_open 创建成功后,可以在”/dev/mqueue"下看到该节点。

mq_close用于关闭一个消息队列,和文件的close类型,关闭后,消息队列并不从系统中删除。一个进程结束,会自动调用关闭打开着的消息队列。

mq_unlink用于删除一个消息队列。消息队列创建后只有通过调用该函数或者是内核自举才能进行删除。每个消息队列都有一个保存当前打开着描述符数的引用计数器,和文件一样,因此本函数能够实现类似于unlink函数删除一个文件的机制。

2.2 POSIX消息队列的属性

一个POSIX消息队列一定有一下四个属性:

long    mq_flags //消息队列的标志:0或O_NONBLOCK,用来表示是否阻塞 
long    mq_maxmsg  //消息队列的最大消息数
long    mq_msgsize  //消息队列中每个消息的最大字节数
long    mq_curmsgs  //消息队列中当前的消息数目

可以使用以下两个函数进行获取与修改

mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr);
mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr);
                               //成功返回0,失败返回-1

mq_getattr用于获取当前消息队列的属性,mq_setattr用于设置当前消息队列的属性。其中mq_setattr中的oldattr用于保存修改前的消息队列的属性,可以为空。

mq_setattr可以设置的属性只有mq_flags,用来设置或清除消息队列的非阻塞标志。newattr结构的其他属性被忽略。mq_maxmsg和mq_msgsize属性只能在创建消息队列时通过mq_open来设置。mq_open只会设置该两个属性,忽略另外两个属性。mq_curmsgs属性只能被获取而不能被设置。

2.3 消息队列的接收与发送

POSIX消息队列可以通过以下函数来进行发送和接收消息:

#include <mqueue.h>
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio);
                     //成功返回0,出错返回-1
 
mqd_t mq_receive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio);
                     //成功返回接收到消息的字节数,出错返回-1
 
#ifdef __USE_XOPEN2K
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio,
                      const struct timespec *abs_timeout);
 
mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio,
                      const struct timespec *abs_timeout);
#endif

mq_send向消息队列中写入一条消息,mq_receive从消息队列中读取一条消息。

1.mqdes:消息队列描述符;
2.msg_ptr:指向消息体缓冲区的指针;
3.msg_len:消息体的长度,其中mq_receive的该参数不能小于能写入队列中消息的最大大小,即一定要大于等于该队列的mq_attr结构中mq_msgsize的大小。如果mq_receive中的msg_len小于该值,就会返回EMSGSIZE错误。POXIS消息队列发送的消息长度可以为0。
4.msg_prio:消息的优先级;它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。POSIX消息队列在调用mq_receive时总是返回队列中最高优先级的最早消息。如果消息不需要设定优先级,那么可以在mq_send是置msg_prio为0,mq_receive的msg_prio置为NULL。

mq_receive从mqdes引用的消息队列中删除一条优先级最高,存放时间最长的消息,将删除的消息保存在msg_ptr指针指向的缓冲区。
1.mqdes: 消息队列描述符
2.msg_ptr:指向存放消息的缓冲区指针
3.msg_len:msg_ptr所指向缓冲区长度,要大于消息队列的mq_msgsize
4.msg_prio:如不为空,接收到的消息的优先级会被复制到指针指向处

还有两个XSI定义的扩展接口限时发送和接收消息的函数:mq_timedsend和mq_timedreceive函数。默认情况下mq_send和mq_receive是阻塞进行调用,可以通过mq_setattr来设置为O_NONBLOCK。

三、代码

由于调查的问题是多进程场景,因此设置了一个进程发送,两个进程接收(两个进程完全异步且优先级一致)

发送进程代码:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <getopt.h>
#include <mqueue.h>
#include <cerrno>
#include <fcntl.h>
#include <iostream>

#define TEST_NODE "/testmQueue"

int main(int argc, char **argv)
{
    mqd_t msg_queue;

    msg_queue = mq_open(TEST_NODE, O_RDWR | O_CREAT, 0666, NULL);
    if (msg_queue < 0)
    {
        std::cout << "open message queue error..." << strerror(errno) << std::endl;
        return -1;
    }

    mq_attr mqAttr;
    if (mq_getattr(msg_queue, &mqAttr) < 0)
    {
        std::cout<<"get the message queue attribute error"<<std::endl;
        return -1;
    }
 
    std::cout<<"mq_flags:"<<mqAttr.mq_flags<<std::endl;
    std::cout<<"mq_maxmsg:"<<mqAttr.mq_maxmsg<<std::endl;
    std::cout<<"mq_msgsize:"<<mqAttr.mq_msgsize<<std::endl;
    std::cout<<"mq_curmsgs:"<<mqAttr.mq_curmsgs<<std::endl;

    std::string text = "TestMessage";
    for (int i = 1; i <= 100; ++i)
    {
        std::string msg = text + std::to_string(i);
        if (mq_send(msg_queue, msg.c_str(), sizeof(msg), 0) < 0)
        {
            std::cout << "send message " << i << " failed. " << std::endl;
            std::cout << "error info:" << strerror(errno) << std::endl;
        }
        std::cout << "send message " << i << " success. " << std::endl;
    }

    return EXIT_SUCCESS;
}

接受进程1:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <getopt.h>
#include <mqueue.h>
#include <cerrno>
#include <fcntl.h>
#include <iostream>

#define TEST_NODE "/testmQueue"
#define BUFF_SIZE 8192 /* mq_msgsize may change in other envirment*/

int main(int argc, char **argv)
{
    mqd_t msg_queue;

    msg_queue = mq_open(TEST_NODE, O_RDONLY, 0666, NULL);
    if (msg_queue < 0)
    {
        std::cout << "open message queue error..." << strerror(errno) << std::endl;
        return -1;
    }
    else
    {
        std::cout << "open message queue successfully" << std::endl;
    }

    char *msg_buf = new char[BUFF_SIZE];
    while (true)
    {
        if (mq_receive(msg_queue, msg_buf, BUFF_SIZE, NULL) < 0)
        {
            std::cout << "receive message  failed. " << std::endl;
            std::cout << "error info:" << strerror(errno) << std::endl;
            continue;
        }

        std::cout << "receive message: " << msg_buf << std::endl;
    }
    return EXIT_SUCCESS;
}

接收进程2:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <getopt.h>
#include <mqueue.h>
#include <cerrno>
#include <fcntl.h>
#include <iostream>

#define TEST_NODE "/testmQueue"
#define BUFF_SIZE 8192

int main(int argc, char **argv)
{
    mqd_t msg_queue;

    msg_queue = mq_open(TEST_NODE, O_RDONLY, 0666, NULL);
    if (msg_queue < 0)
    {
        std::cout << "open message queue error..." << strerror(errno) << std::endl;
        return -1;
    }
    else
    {
        std::cout << "open message queue successfully" << std::endl;
    }

    char *msg_buf = new char[BUFF_SIZE];
    while (true)
    {
        if (mq_receive(msg_queue, msg_buf, BUFF_SIZE, NULL) < 0)
        {
            std::cout << "receive message  failed. " << std::endl;
            std::cout << "error info:" << strerror(errno) << std::endl;
            continue;
        }

        std::cout << "receive message: " << msg_buf << std::endl;
    }

    return EXIT_SUCCESS;
}

四、测试结果

在这里插入图片描述
在这里插入图片描述

可以看到两个进程都接收到了消息,但并不是“均匀”地接收到。原因主要是因为系统调度导致各个进程获得的时间片不一致也与但是系统的运行状态有关。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值