目录
在 RabbitMQ 中,提供服务的是信道,因此在客户端的实现中,弱化了 Client 客户端的概念,也就是说在 RabbitMQ 中并不会向用户展示网络通信的概念出来,而是以一种提供服务的形式来体现。
其实现思想类似于普通的功能接口封装,一个接口实现一个功能,接口内部完成向客户端请求的过程,但是对外并不需要体现出客户端与服务端通信的概念,用户需要什么服务就调用什么接口就行。
基于以上的思想,客户端的实现共分为四大模块:
- 订阅者模块
- 一个并不直接对用户展示的模块,其在客户端体现的作用就是对于角色的描述,表示这是一个消费者。
- 信道模块
- 一个直接面向用户的模块,内部包含多个向外提供的服务接口,用户需要什么服务,调用对应接口即可。
- 其包含交换机声明/删除,队列声明/删除,绑定/解绑,消息发布/确认,订阅/解除订阅等服务。
- 连接模块
- 这是唯一能体现出网络通信概念的一个模块了,它向用户提供的功能就是用于打开/关闭信道。
- 异步线程模块
- 虽然客户端部分,并不对外体现网络通信的概念,但是本质上内部还是包含有网络通信的,因此既然有网络通信,那么就必须包含有一个网络通信 IO 事件监控线程模块,用于进行客户端连接的 IO 事件监控,以便于在事件出发后进行 IO操作。
- 其次,在客户端部分存在一个情况就是,当一个信道作为消费者而存在的时候,服务端会向信道推送消息,而用户这边需要对收到的消息进行不同的业务处理,而这个消息的处理需要一个异步的工作线程池来完成。
- 因此异步线程模块包含两个部分:▪ 客户端连接的 IO 事件监控线程▪ 推送过来的消息异步处理线程。
基于以上模块,实现一个客户端的流程也就比较简单了
- 实例化异步线程对象
- 实例化连接对象
- 通过连接对象,创建信道
- 根据信道获取自己所需服务
- 关闭信道
- 关闭连接
订阅者模块
与服务端,并无太大差别,客户端这边虽然订阅者的存在感微弱了很多,但是还是有的,当进行队列消息订阅的时候,会伴随着一个订阅者对象的创建,而这个订阅者对象有以下几个作用:
- 描述当前信道订阅了哪个队列的消息。
- 描述了收到消息后该如何对这条消息进行处理。
- 描述收到消息后是否需要进行确认回复。
订阅者信息:
- 订阅者标识
- 订阅队列名
- 是否自动确认标志
- 回调处理函数(收到消息后该如何处理的回调函数对象)
cpp
#pragma once
#include "../mqcommon/helper.hpp"
#include "../mqcommon/log.hpp"
#include "../mqcommon/msg.pb.h"
#include <unordered_map>
#include <memory>
#include <functional>
namespace jiuqi
{
// 消费者表示, 属性, 消息
using ConsumerCallback = std::function<void(const std::string&, const BasicProperties*, const std::string&)>;
struct Consumer
{
using ptr = std::shared_ptr<Consumer>;
std::string tag; // 消费者标识
std::string qname; // 绑定的队列名称
bool auto_ack; // 是否自动应答
ConsumerCallback callback; // 回调函数
Consumer() { DEBUG("new Consumer %p", this); }
Consumer(const std::string &ctag, const std::string &queue_name, bool ack, const ConsumerCallback &cb)
: tag(ctag), qname(queue_name), auto_ack(ack), callback(cb) { DEBUG("new Consumer %p", this); }
~Consumer() { DEBUG("del Consumer %p", this); }
};
}
信道管理模块
同样的,客户端也有信道,其功能与服务端几乎一致,或者说不管是客户端的channel 还是服务端的 channel 都是为了用户提供具体服务而存在的,只不过服务端是为客户端的对应请求提供服务,而客户端的接口服务是为了用户具体需要服务,也可以理解是用户通过客户端 channel 的接口调用来向服务端发送对应请求,获取请求的服务。
- 信道信息:
- 信道 ID
- 信道关联的网络通信连接对象
- protobuf 协议处理对象
- 信道关联的消费者
- 请求对应的响应信息队列(这里队列使用<请求 ID,响应>hash 表,以便于查找指定的响应)
- 互斥锁&条件变量(大部分的请求都是阻塞操作,发送请求后需要等到响应才能继续,但是 muduo 库的通信是异步的,因此需要我们自己在收到响应后,通过判断是否是等待的指定响应来进行同步)
-
- 信道操作:
- 提供创建信道操作
- 提供删除信道操作
- 提供声明交换机操作(强断言-有则 OK,没有则创建)
- 提供删除交换机
- 提供创建队列操作(强断言-有则 OK,没有则创建)
- 提供删除队列操作
- 提供交换机-队列绑定操作
- 提供交换机-队列解除绑定操作
- 提供添加订阅操作
- 提供取消订阅操作
- 提供发布消息操作
- 提供确认消息操作
- 信道管理:
- 创建信道
- 查询信道
- 删除信道
cpp
#pragma once
#include "muduo/net/TcpConnection.h"
#include "muduo/proto/codec.h"
#include "muduo/proto/dispatcher.h"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/log.hpp"
#include "../mqcommon/msg.pb.h"
#include "../mqcommon/proto.pb.h"
#include "../mqcommon/threadpool.hpp"
#include "consumer.hpp"
#include <unordered_map>
#include <condition_variable>
#include <memory>
namespace jiuqi
{
using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
using basicConsumeResponsePtr = std::shared_ptr<basicConsumeResponse>;
using basicCommonResponsePtr = std::shared_ptr<basicCommonResponse>;
class Channel
{
public:
using ptr = std::shared_ptr<Channel>;
Channel(const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec)
: _cid(UUIDHelper::uuid()), _conn(conn), _codec(codec), _consumer(nullptr) {}
~Channel()
{
basicCancel();
}
std::string cid() { return _cid; }
bool openChannel()
{
openChannelRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void closeChannel()
{
closeChannelRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
}
bool declareExchange(const std::string &ename,
ExchangeType etype,
bool edurable,
bool eauto_delete,
google::protobuf::Map<std::string, std::string> &eargs)
{
// 构造请求对象
declareExchangeRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_ename(ename);
req.set_etype(etype);
req.set_durable(edurable);
req.set_auto_delete(eauto_delete);
req.mutable_args()->swap(eargs);
// 向服务器发送请求
_codec->send(_conn, req);
// 等待相应
basicCommonResponsePtr resp = waitResponse(rid);
// 返回
return resp->ok();
}
void deleteExchange(const std::string &ename)
{
deleteExchangeRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_ename(ename);
_codec->send(_conn, req);
waitResponse(rid);
}
bool declareQueue(const std::string &qname,
bool qdurable,
bool qexclusive,
bool qauto_delete,
google::protobuf::Map<std::string, std::string> qargs)
{
declareQueueRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_qname(qname);
req.set_durable(qdurable);
req.set_auto_delete(qauto_delete);
req.set_exclusive(qexclusive);
req.mutable_args()->swap(qargs);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void deleteQueue(const std::string &qname)
{
deleteQueueRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_qname(qname);
_codec->send(_conn, req);
waitResponse(rid);
}
bool queueBind(const std::string &ename,
const std::string &qname,
const std::string &key)
{
queueBindRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_qname(qname);
req.set_ename(ename);
req.set_bindingkey(key);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void queueUnbind(const std::string &ename, const std::string &qname)
{
queueUnbindRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_ename(ename);
req.set_qname(qname);
_codec->send(_conn, req);
waitResponse(rid);
}
void basicPublish(const std::string &ename,
const BasicProperties *bp,
const std::string &body)
{
basicPublishRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_ename(ename);
req.set_body(body);
if (bp != nullptr)
{
req.mutable_properties()->set_id(bp->id());
req.mutable_properties()->set_deliver_mode(bp->deliver_mode());
req.mutable_properties()->set_routing_key(bp->routing_key());
}
_codec->send(_conn, req);
waitResponse(rid);
}
void basicAck(const std::string &msgid)
{
if (_consumer == nullptr)
{
DEBUG("消息确认时没有找到消费者");
return;
}
basicAckRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_qname(_consumer->qname);
req.set_mid(msgid);
_codec->send(_conn, req);
waitResponse(rid);
}
void basicCancel()
{
if (_consumer == nullptr)
{
DEBUG("取消订阅时没有找到消费者");
return;
}
basicCancelRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_qname(_consumer->qname);
req.set_ctag(_consumer->tag);
_codec->send(_conn, req);
waitResponse(rid);
_consumer = nullptr;
}
bool basicConsumer(const std::string &consumer_tag,
const std::string &qname,
bool auto_ack,
const ConsumerCallback &cb)
{
if (_consumer != nullptr)
{
DEBUG("当前信道已订阅其他信道");
return false;
}
basicConsumeRequest req;
std::string rid = UUIDHelper::uuid();
req.set_rid(rid);
req.set_cid(_cid);
req.set_ctag(consumer_tag);
req.set_qname(qname);
req.set_auto_ack(auto_ack);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
if (resp->ok() == false)
{
DEBUG("添加订阅失败");
return false;
}
_consumer = std::make_shared<Consumer>(consumer_tag, qname, auto_ack, cb);
return true;
}
public:
// 连接收到基础响应后, 向hash中添加
void putBasicResponse(const basicCommonResponsePtr &resp)
{
std::unique_lock<std::mutex> lock(_mutex);
_basic_resp.insert(std::make_pair(resp->rid(), resp));
_cv.notify_all();
}
// 连接收到消息推送后, 需要通过信道找到对应的消费者对象, 通过回调函数处理数据
void consume(const basicConsumeResponsePtr &resp)
{
if (_consumer == nullptr)
{
DEBUG("消息处理时, 未找到订阅者信息");
return;
}
if (resp->ctag() != _consumer->tag)
{
DEBUG("收到的推送消息中的消费者标识与当前信道的消费者标识不一致: %s %s", resp->ctag().c_str(), _consumer->tag.c_str());
return;
}
_consumer->callback(resp->ctag(), resp->mutable_properties(), resp->body());
}
private:
basicCommonResponsePtr waitResponse(const std::string &rid)
{
std::unique_lock<std::mutex> lock(_mutex);
_cv.wait(lock, [&rid, this]()
{ return _basic_resp.find(rid) != _basic_resp.end(); });
basicCommonResponsePtr basic_resp = _basic_resp[rid];
_basic_resp.erase(rid);
return basic_resp;
}
private:
std::string _cid;
muduo::net::TcpConnectionPtr _conn;
ProtobufCodecPtr _codec;
Consumer::ptr _consumer;
std::mutex _mutex;
std::condition_variable _cv;
std::unordered_map<std::string, basicCommonResponsePtr> _basic_resp;
};
class ChannelManager
{
public:
using ptr = std::shared_ptr<ChannelManager>;
ChannelManager() {}
Channel::ptr create(const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec)
{
std::unique_lock<std::mutex> lock(_mutex);
auto channel = std::make_shared<Channel>(conn, codec);
_channels.insert(std::make_pair(channel->cid(), channel));
return channel;
}
void remove(const std::string &cid)
{
std::unique_lock<std::mutex> lock(_mutex);
_channels.erase(cid);
}
Channel::ptr get(const std::string &cid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(cid);
if (it == _channels.end())
return nullptr;
return it->second;
}
private:
std::mutex _mutex;
std::unordered_map<std::string, Channel::ptr> _channels;
};
}
异步工作线程实现
客户端这边存在两个异步工作线程:
- 一个是 muduo 库中客户端连接的异步循环线程 EventLoopThread,
- 一个是当收到消息后进行异步处理的工作线程池。
这两项都不是以连接为单元进行创建的,而是创建后,可以用以多个连接中,因此单独进行封装。
cpp
#pragma once
#include "muduo/net/EventLoopThread.h"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/log.hpp"
#include "../mqcommon/msg.pb.h"
#include "../mqcommon/proto.pb.h"
#include "../mqcommon/threadpool.hpp"
namespace jiuqi
{
class AsyncWorker
{
public:
using ptr = std::shared_ptr<AsyncWorker>;
muduo::net::EventLoopThread loopthread;
ThreadPool pool;
};
}
连接管理模块
在客户端这边,RabbitMQ 弱化了客户端的概念,因为用户所需的服务都是通过信道来提供的,因此操作思想转换为先创建连接,通过连接创建信道,通过信道提供服务这一流程。
这个模块同样是针对 muduo 库客户端连接的二次封装,向用户提供创建 channel 信道的接口,创建信道后,可以通过信道来获取指定服务。
cpp
#pragma once
#include "muduo/proto/dispatcher.h"
#include "muduo/proto/codec.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/base/CountDownLatch.h"
#include "channel.hpp"
#include "worker.hpp"
namespace jiuqi
{
class Connection
{
public:
using ptr = std::shared_ptr<Connection>;
using MessagePtr = std::shared_ptr<google::protobuf::Message>;
Connection(const std::string ip, int port, AsyncWorker::ptr &worker)
: _latch(1),
_client(worker->loopthread.startLoop(), muduo::net::InetAddress(ip, port), "connection"),
_dispatcher(std::bind(&Connection::onUnknownMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)),
_codec(std::make_shared<ProtobufCodec>(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))),
_worker(worker), _channel_manager(std::make_shared<ChannelManager>())
{
// 注册请求处理函数
_dispatcher.registerMessageCallback<basicCommonResponse>(std::bind(&Connection::basicResponse, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
_dispatcher.registerMessageCallback<basicConsumeResponse>(std::bind(&Connection::consumeResponse, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, _codec.get(),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
_client.setConnectionCallback(std::bind(&Connection::onConnection, this, std::placeholders::_1));
_client.connect();
_latch.wait(); // 阻塞等待
}
Channel::ptr openChannel()
{
Channel::ptr channel = _channel_manager->create(_conn, _codec);
bool ret = channel->openChannel();
if (ret == false)
{
DEBUG("打开信道失败");
return nullptr;
}
return channel;
}
void closeChannel(const Channel::ptr &channel)
{
channel->closeChannel();
_channel_manager->remove(channel->cid());
}
private:
void basicResponse(const muduo::net::TcpConnectionPtr &conn, const basicCommonResponsePtr &message, muduo::Timestamp)
{
Channel::ptr channel = _channel_manager->get(message->cid());
if (channel == nullptr)
{
DEBUG("未找到信道");
return;
}
channel->putBasicResponse(message);
}
void consumeResponse(const muduo::net::TcpConnectionPtr &conn, const basicConsumeResponsePtr &message, muduo::Timestamp)
{
Channel::ptr channel = _channel_manager->get(message->cid());
if (channel == nullptr)
{
DEBUG("未找到信道");
return;
}
_worker->pool.push([channel, message]()
{ channel->consume(message); });
}
void onUnknownMessage(const muduo::net::TcpConnectionPtr &,
const MessagePtr &message,
muduo::Timestamp)
{
LOG_INFO << "onUnknownMessage: " << message->GetTypeName();
}
void onConnection(const muduo::net::TcpConnectionPtr &conn)
{
if (conn->connected())
{
_conn = conn;
std::cout << "连接建立成功, 准备countDown" << std::endl;
_latch.countDown();
std::cout << "countDown完成" << std::endl;
}
else
{
std::cout << "连接关闭" << std::endl;
}
}
private:
muduo::CountDownLatch _latch; // 实现同步的
AsyncWorker::ptr _worker;
muduo::net::TcpClient _client;
ProtobufDispatcher _dispatcher; // 请求分发器
ProtobufCodecPtr _codec; // 协议处理器
muduo::net::TcpConnectionPtr _conn; // 客户端对应连接
ChannelManager::ptr _channel_manager;
};
}