使用Qt实现消息队列中间件动态库封装

一 概述

本文主要是基于RabbitMQ消息队列,实现一个消息队列中间件动态库.该动态库包括的功能有:

1.连接RabbitMQ服务;

2.设置发送队列参数;

3.设置接收队列参数;

4.发送单播消息到指定队列;

5.发送广播消息到所有队列;

6.开启消费队列;

7.断开与RabbitMQ服务连接.

二 封装接口

cpp 复制代码
#ifndef IMSMESSAGEQUEUE_H
#define IMSMESSAGEQUEUE_H

#include <QtCore/qglobal.h>
#include <memory>
#include <functional>
using namespace std;

using MessageCallback = std::function<void(const std::string&)>;

#if defined(IMSMESSAGEQUEUE_LIBRARY)
#  define IMSMESSAGEQUEUE_EXPORT Q_DECL_EXPORT
#else
#  define IMSMESSAGEQUEUE_EXPORT Q_DECL_IMPORT
#endif
class IMessageQueue;
class IMSMESSAGEQUEUE_EXPORT IMSMessageQueue
{
public:
    IMSMessageQueue();
    /**
     * @brief Connect 连接服务器
     * @param host
     * @param port
     * @param username
     * @param password
     * @param vhost
     * @return
     */
    bool Connect(const std::string& host, int port,
                     const std::string& username,
                     const std::string& password,
                     const std::string &vhost);
    /**
     * @brief DisConnect 断开连接
     */
    void DisConnect();
    /**
     * @brief setSendParams 设置发送参数
     * @param queueName 发送队列名称
     * @param durable true-消息持久化 false-消息非持久化
     */
    void SetSendParams(const std::string &queueName,const string exchangeName="", bool durable=true);
    /**
     * @brief setRecvParams 设置接收参数
     * @param queueName 接收队列名称
     * @param callback 接收消息回调
     * @param exchangeName 交换机名称
     * @param durable true-消息持久化 false-消息非持久化
     */
    void SetRecvParams(const std::string& queueName,
                         MessageCallback callback,
                       const string exchangeName="",
                       bool durable=true);
    /**
     * @brief Send 发送消息到服务器指定队列
     * @param message
     * @return
     */
    bool Send(const std::string& message);
    /**
     * @brief SendBroadcast 发送广播消息
     * @param exchange 交换机名称
     * @param message 消息
     * @return
     */
    bool SendBroadcast(const std::string&exchange,const std::string& message);
    /**
     * @brief StartConsuming 开启消费队列
     */
    void StartConsuming();

private:
    IMSMessageQueue(const IMSMessageQueue &) = delete;
    IMSMessageQueue &operator=(const IMSMessageQueue &) = delete;
private:
    std::unique_ptr<IMessageQueue> _mq;
};

#endif // IMSMESSAGEQUEUE_H

三 关键接口实现

1.连接RabbitMQ服务接口实现

cpp 复制代码
bool RabbitMQAdapter::connect(const std::string &host,
                              int port,
                              const std::string &username,
                              const std::string &password,
                              const std::string &vhost)
{
    std::string connectParam;
    connectParam = "amqp://"+username+":"+password+"@"+host+":"+to_string(port)+vhost;

    if(_consumer){
        _consumer->init(connectParam);
    }
    std::cout << "连接地址:" << connectParam << endl;
    if (_loop == nullptr)
    {
        std::cerr << "【初始化连接】事件循环无效,无法初始化连接" << std::endl;
        return false;
    }
    std::string exchangeName = _exchangeName;
    try
    {
        _handler = std::make_unique<CustomLibEvHandler>(_loop);
        AMQP::Address address(connectParam);
        _connection = std::make_unique<AMQP::TcpConnection>(_handler.get(), address);

        // 启动事件循环线程(关键步骤)
        _io_thread = std::thread([=] {
            ev_run(_loop, 0);  // 阻塞在此处理网络事件
        });

        _channel = std::make_unique<AMQP::TcpChannel>(_connection.get());

#if 1
        if(exchangeName != ""){//如果交换机名称为空,则不发送广播消息
            // 声明fanout类型交换机(广播专用)
            _channel->declareExchange(exchangeName, AMQP::fanout,AMQP::durable | AMQP::autodelete)
                    .onSuccess([=](){
                std::cerr << "交换机声明成功: " << exchangeName << std::endl;
            })
                    .onError([=](const char* error){
                std::cerr << "交换机声明失败: " << error << std::endl;
            });
        }
#endif

        // 改造核心:队列声明成功后,标记就绪并触发批量发送
        if(_durable){
            _channel->declareQueue(
                        std::string_view(_productQueueName.data(), _productQueueName.size()),
                        AMQP::durable
                        )
                    .onSuccess([=](const std::string &name, uint32_t message_count, uint32_t consumer_count) {
                std::cout << "【初始化连接】队列声明成功:" << name
                          << ",当前消息数:" << message_count
                          << ",消费者数:" << consumer_count << std::endl;
                _isInitSuccess = true; // 标记初始化成功

                // 关键:队列就绪后,立即发送缓存中的消息
                this->_sendBatchMessages();
            })
                    .onError([this](const char *message) {
                std::cerr << "【初始化连接】队列声明失败:" << message << std::endl;
                _isInitSuccess = false;
                if (_loop != nullptr)
                {
                    ev_break(_loop, EVBREAK_ALL);
                }
            });
        }else{
            _channel->declareQueue(
                        std::string_view(_productQueueName.data(), _productQueueName.size()),
                        0
                        )
                    .onSuccess([=](const std::string &name, uint32_t message_count, uint32_t consumer_count) {
                std::cout << "【初始化连接】队列声明成功:" << name
                          << ",当前消息数:" << message_count
                          << ",消费者数:" << consumer_count << std::endl;
                _isInitSuccess = true; // 标记初始化成功

                // 关键:队列就绪后,立即发送缓存中的消息
                this->_sendBatchMessages();
            })
                    .onError([this](const char *message) {
                std::cerr << "【初始化连接】队列声明失败:" << message << std::endl;
                _isInitSuccess = false;
                if (_loop != nullptr)
                {
                    ev_break(_loop, EVBREAK_ALL);
                }
            });
        }

        return true;
    }
    catch (const std::exception &e)
    {
        std::cerr << "【初始化连接】异常:" << e.what() << std::endl;
        return false;
    }
}

2. 发送单播消息接口实现

cpp 复制代码
bool RabbitMQAdapter::sendMessage(const string &message)
{
    AMQP::Envelope envelope(message.data(), message.size());
    envelope.setDeliveryMode(2);

    bool sendResult = _channel->publish("", _productQueueName, envelope);
    if (sendResult)
    {
        std::cout << "【发送消息】成功:" << message << std::endl;
        return true;
    }
    else
    {
        std::cerr << "【发送消息】失败:" << message << std::endl;
        return false;
    }
}

3.发送广播消息接口实现

cpp 复制代码
bool RabbitMQAdapter::sendBroadcast(const string &exchangeName, const string &message)
{
    if(!_channel->usable())
        return false;
    // 创建消息信封
    AMQP::Envelope envelope(message.data(), message.size());
    envelope.setDeliveryMode(2); // 持久化消息

    // 发送广播消息(路由键为空)
    _channel->publish(exchangeName, "", envelope);
    std::cout << "广播消息已发送: " << message << std::endl;
    return true;
}

4.开启消费队列接口实现

cpp 复制代码
void RabbitMQAdapter::startConsuming()
{
    if(_consumer){
        _consumer->doConsuming();
    }
}
void RabbitMQConsumer::doConsuming()
{
    _consumThread =  std::thread([=](){
        this->ConsumingImpl();
    });
}
void RabbitMQConsumer::ConsumingImpl()
{
    RabbitMQConsumer *ctx = this;
    // 1. 获取LibEv默认事件循环
       struct ev_loop *loop = ev_loop_new(EVFLAG_AUTO);
       if (!loop)
       {
           std::cerr << "获取libev事件循环失败" << std::endl;
           return ;
       }

       // 2. 创建自定义LibEv处理器
       CustomLibEvHandler handler(loop);

       // 3. 创建TCP连接(与生产者使用相同的RabbitMQ配置)
       AMQP::Address address(_connectParam);
       AMQP::TcpConnection connection(&handler, address);

       // 4. 创建Channel(队列声明、消息消费的核心载体)
       AMQP::TcpChannel channel(&connection);

       // 5. 定义队列名称(与生产者保持一致,若使用持久化队列则对应修改名称)
       std::string queue_name = _consumeQueueName; // 若生产者使用了 test_queue_ev_durable,此处需对应修改

       // 6. 创建自定义消息处理器实例
       CustomMessageHandler messageHandler(&channel);

       // 7. 声明队列(与生产者队列参数保持一致,避免配置冲突)
       // 若生产者已创建持久化队列,此处添加 AMQP::durable;否则使用 0(非持久化)
       if(_recvQueueDurable){
           channel.declareQueue(
                       std::string_view(queue_name.data(), queue_name.size()),
                       AMQP::durable // 与生产者队列配置保持一致,若为持久化队列则改为 AMQP::durable
                       )
                   .onSuccess([&channel, &messageHandler,queue_name,ctx](
                              const std::string &name,
                              uint32_t message_count,
                              uint32_t consumer_count) {
               std::cout << "队列声明成功(消费者):" << name << std::endl;
               std::cout << "队列当前消息数:" << message_count << ",当前消费者数:" << consumer_count << std::endl;

               //7.1 绑定交换机与队列
#if 1
               std::string exchangeName = ctx->_recvExchangeName;
                if(exchangeName != ""){
                const AMQP::Table table;
                channel.bindQueue(exchangeName,ctx->_consumeQueueName,"",table)
                        .onSuccess([=](){
                            std::cout << "接收方:绑定交换机:" << exchangeName <<" 与消息队列:" << ctx->_consumeQueueName <<"成功" << std::endl;
                        })
                        .onError([=](const char *message){
                            std::cout << "接收方:绑定交换机:" << exchangeName <<" 与消息队列:" << ctx->_consumeQueueName <<"失败; 错误信息:" << message  << std::endl;
                        });
                }
#endif

               // 8. 开始消费队列中的消息(核心消费逻辑)
               channel.consume(queue_name)
                       // 核心修改:1. 修正 Message 为 const 引用 2. 确保捕获列表清晰 3. 签名与标准完全一致
                       .onMessage([&messageHandler,ctx](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) {
                   // 转发消息到自定义消息处理器(逻辑不变,仅参数传递符合标准签名)
                   messageHandler.onMessageReceived(message, deliveryTag, redelivered);
                   if(ctx && ctx->callback_){
                       std::string messageContent(message.body(), message.bodySize());
                       ctx->callback_(messageContent);
                   }
               })
                       .onSuccess([](const std::string &consumerTag) {
                   std::cout << "消费启动成功,消费者标签(consumerTag):" << consumerTag << std::endl;
               })
                       .onError([](const char *message) {
                   std::cerr << "消费启动失败:" << message << std::endl;
               });

           })
                   .onError([loop](const char *message) {
               std::cerr << "队列声明失败(消费者):" << message << std::endl;
               if (loop != nullptr)
               {
                   ev_break(loop, EVBREAK_ALL); // 声明失败,退出事件循环
               }
           });
       }else{
           channel.declareQueue(
                       std::string_view(queue_name.data(), queue_name.size()),
                       0
                       )
                   .onSuccess([&channel, &messageHandler,queue_name,ctx](const std::string &name, uint32_t message_count, uint32_t consumer_count) {
               std::cout << "队列声明成功(消费者):" << name << std::endl;
               std::cout << "队列当前消息数:" << message_count << ",当前消费者数:" << consumer_count << std::endl;
               //7.1 绑定交换机与队列
#if 1
               std::string exchangeName = ctx->_recvExchangeName;
                if(exchangeName != ""){
                const AMQP::Table table;
                channel.bindQueue(exchangeName,ctx->_consumeQueueName,"",table)
                        .onSuccess([=](){
                            std::cout << "接收方:绑定交换机:" << exchangeName <<" 与消息队列:" << ctx->_consumeQueueName <<"成功" << std::endl;
                        })
                        .onError([=](const char *message){
                            std::cout << "接收方:绑定交换机:" << exchangeName <<" 与消息队列:" << ctx->_consumeQueueName <<"失败; 错误信息:" << message  << std::endl;
                        });
                }
#endif
               // 8. 开始消费队列中的消息(核心消费逻辑)
               channel.consume(queue_name)
                       // 核心修改:1. 修正 Message 为 const 引用 2. 确保捕获列表清晰 3. 签名与标准完全一致
                       .onMessage([&messageHandler,ctx](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) {
                   // 转发消息到自定义消息处理器(逻辑不变,仅参数传递符合标准签名)
                   messageHandler.onMessageReceived(message, deliveryTag, redelivered);
                   if(ctx && ctx->callback_){
                       std::string messageContent(message.body(), message.bodySize());
                       ctx->callback_(messageContent);
                   }
               })
                       .onSuccess([](const std::string &consumerTag) {
                   std::cout << "消费启动成功,消费者标签(consumerTag):" << consumerTag << std::endl;
               })
                       .onError([](const char *message) {
                   std::cerr << "消费启动失败:" << message << std::endl;
               });
           })
                   .onError([loop](const char *message) {
               std::cerr << "队列声明失败(消费者):" << message << std::endl;
               if (loop != nullptr)
               {
                   ev_break(loop, EVBREAK_ALL); // 声明失败,退出事件循环
               }
           });
       }
       // 9. 启动LibEv事件循环,持续监听并消费消息(无阻塞,一直运行直到手动终止)
       std::cout << "消费者启动成功,开始监听队列消息(按 Ctrl+C 终止)..." << std::endl;
       ev_run(loop, 0);

       // 10. 事件循环退出后的清理(此处仅打印日志,可添加自定义清理逻辑)
       std::cout << "消费者运行结束" << std::endl;
}

四 调用流程

a.创建一个消息队列实例;

b.设置发送消息参数,设置接收消息参数;

c.连接RabbitMQ服务;

d.开启消费队列(可选);

e.发送单播(广播)消息;

f.断开连接.

测试程序如下:

cpp 复制代码
#include <QCoreApplication>
#include "imsmessagequeue.h"
#include <QDebug>
#include <QTimer>
#define SEND_QUEUENAME "alarm-queue"
#define RECV_QUEUENAME "alarm-queue"
#define HOST "192.168.22.196"
#define PORT 5672
#define USERNAME "admin"
#define PASSWORD "123456"
#define VHOST "/"
static void dealRecvMsg(const std::string msg);
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    std::string exchangeName = "broadcast.notify";
    IMSMessageQueue *_msgQueue = new  IMSMessageQueue();
    _msgQueue->SetSendParams(SEND_QUEUENAME,exchangeName);
    _msgQueue->SetRecvParams(RECV_QUEUENAME,dealRecvMsg,exchangeName);
    bool ret = _msgQueue->Connect(HOST,PORT,USERNAME,PASSWORD,VHOST);
    if(ret){
        qDebug() << "连接服务成功";
    }else{
        qDebug() << "连接服务失败";
    }
    _msgQueue->StartConsuming();

    QTimer::singleShot(5000,[=](){
        _msgQueue->Send("这是一条单播消息1 ");
        _msgQueue->Send("这是一条单播消息2 ");

        _msgQueue->Send("这是一条单播消息3");
        _msgQueue->Send("这是一条单播消息4");
        _msgQueue->SendBroadcast(exchangeName,"这是一个广播消息");
        _msgQueue->SendBroadcast(exchangeName,"这是一个广播消息1");
    });

    return a.exec();
}
void dealRecvMsg(const std::string msg)
{
    qDebug() << "接收到消息:" << msg.c_str();
}

五 运行效果

ProductMQ运行结果:

ConsumeMQ运行结果:

相关推荐
N.D.A.K2 小时前
CF2138C-Maple and Tree Beauty
c++·算法
AI视觉网奇2 小时前
ue 5.5 c++ mqtt 订阅/发布 json
网络·c++·json
程序员-King.2 小时前
day159—动态规划—打家劫舍(LeetCode-198)
c++·算法·leetcode·深度优先·回溯·递归
txinyu的博客2 小时前
解析muduo源码之 StringPiece.h
开发语言·网络·c++
墨雪不会编程2 小时前
C++【string篇4】string结尾篇——字符编码表、乱码的来源及深浅拷贝
android·开发语言·c++
小丑小丑小丑3 小时前
【AP AUTOSAR】COM通信模块api详解
中间件·汽车·autosar·autosar ap
小钟不想敲代码3 小时前
RabbitMQ高级
分布式·rabbitmq
CSDN_RTKLIB3 小时前
【map应用】组合键统计
c++·stl
txinyu的博客3 小时前
解析muduo源码之 TimeZone.h & TimeZone.cc
linux·服务器·网络·c++