1. 客户端实现思路
在 RabbitMQ 中,提供服务的是信道,因此在客户端的实现中,弱化了 Client 客户端的概念,也就是说在 RabbitMQ 中并不会向用户展示网络通信的概念出来,而是以一种提供服务的形式来体现。
其实现思想类似于普通的功能接口封装,一个接口实现一个功能,接口内部完成向客户端请求的过程,但是对外并不需要体现出客户端与服务端通信的概念,用户需要什么服务就调用什么接口就行。
基于以上的思想,客户端的实现共分为四大模块:
- 订阅者模块:
- 一个并不直接对用户展示的模块,其在客户端体现的作用就是对于角色的描 述,表示这是一个消费者
- 信道模块
- 一个直接面向用户的模块,内部包含多个向外提供的服务接口,用户需要什么服务,调用对应接口即可○ 其包含交换机声明/删除,队列声明/删除,绑定/解绑,消息发布/确认,订阅/解除订阅等服务。
- 连接模块
- 这是唯一能体现出网络通信概念的一个模块了,它向用户提供的功能就是用于打开/关闭信道。
- 异步线程模块
- 虽然客户端部分,并不对外体现网络通信的概念,但是本质上内部还是包含有网络通信的,因此既然有网络通信,那么就必须包含有一个网络通信 IO 事件监控线程模块,用于进行客户端连接的 IO 事件监控,以便于在事件出发后进行 IO 操作。
- 其次,在客户端部分存在一个情况就是,当一个信道作为消费者而存在的时 候,服务端会向信道推送消息,而用户这边需要对收到的消息进行不同的业务处理,而这个消息的处理需要一个异步的工作线程池来完成。
- 因此异步线程模块包含两个部分:
- 客户端连接的 IO 事件监控线程
- 推送过来的消息异步处理线程
基于以上模块,实现一个客户端的流程也就比较简单了
- 实例化异步线程对象
- 实例化连接对象
- 通过连接对象,创建信道
- 根据信道获取自己所需服务
- 关闭信道
- 关闭连接
2. 订阅者模块
与服务端,并无太大差别,客户端这边虽然订阅者的存在感微弱了很多,但是还是有的,当进行队列消息订阅的时候,会伴随着一个订阅者对象的创建,而这个订阅者对象有以下几个作用:
- 描述当前信道订阅了哪个队列的消息。
- 描述了收到消息后该如何对这条消息进行处理。
- 描述收到消息后是否需要进行确认回复。
所以,订阅者模块并不直接对用户展示,它是对消费者角色的描述。当用户通过信道订阅队列时,内部会创建一个订阅者对象,该对象负责处理从服务端推送过来的消息。
设计要点:
- 每个订阅者对应一个消费者标签(consumer_tag)和一个队列(queue_name)。
- 订阅者需要提供一个回调函数,当消息到达时,异步线程模块会调用这个回调函数。
- 订阅者可能支持自动确认或手动确认模式。
因此订阅者信息:
- 订阅者标识
- 订阅队列名
- 是否自动确认标志
- 回调处理函数(收到消息后该如何处理的回调函数对象)
cpp
#ifndef __M_CONSUMER_H__
#define __M_CONSUMER_H__
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <vector>
#include <functional>
namespace rabbitmq
{
using ConsumerCallback = std::function<void(const std::string, const BasicProperties *bp, const std::string)>;
struct Consumer
{
using ptr = std::shared_ptr<Consumer>;
std::string _tag; //消费者标识
std::string _qname; //消费者订阅的队列名称
bool _auto_ack; //自动确认标志
ConsumerCallback _callback;
Consumer()
{
DLOG("new Consumer: %p", this);
}
Consumer(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb)
:_tag(ctag), _qname(queue_name), _auto_ack(ack_flag), _callback(std::move(cb))
{
DLOG("new Consumer: %p", this);
}
~Consumer()
{
DLOG("del Consumer: %p", this);
}
};
}
#endif