C++轻量级消息队列服务器

轻量级消息队列服务器

一、第三方库介绍

1 Protobuf

ProtoBuf(全称 Protocol Buffer)是数据结构序列化和反序列化框架,它具有以下特点:

• 语言无关、平台无关:即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台

• 高效:即比 XML 更小、更快、更为简单

• 扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序

(1) protobuf使用

  • .proto文件

    编写 .proto 文件,目的是为了定义结构对象(message)及属性内容

  • 使用protoc编译.proto文件,生成一系列接口代码

    生成的.h文件定义了我们所描述的数据结构对象类 ; .cc定义实现了结构化对象数据的访问&操作&序列化&反序列化

  • 依赖生成的接口

    将编译生成的头文件包含我们的代码中。方便我们进行使用

Protobufe 语法格式

bash 复制代码
syntax = "proto3";//语法版本选择

package contacts; //命名空间

message contact{
    //字段描述   类型 名称=字段编号
    uint64 sn=1;
    string name=2;
    float score=3;
}

编译proto文件

bash 复制代码
protoc --cpp_out=. contacts.proto

(2) Protobuf接口认识和使用

继承关系:

在我们写的proto文件被编译之后,会看到我们自定义类继承自Message这个类。而Message又继承自MessageLite这个类

contacts(自定义类的名称)---继承---Message---继承---MessageLite

  • 认识MessageLite成员接口:
cpp 复制代码
class MessageLite {
public:
 //序列化:
 bool SerializeToOstream(ostream* output) const; // 将序列
化后数据写入文件流
 bool SerializeToArray(void *data, int size) const;
 bool SerializeToString(string* output) const;
 
 //反序列化:
 bool ParseFromIstream(istream* input); // 从流中读取数
据,再进行反序列化动作
 bool ParseFromArray(const void* data, int size);
 bool ParseFromString(const string& data);
};
  • 使用MessageLite成员接口
cpp 复制代码
#include"contacts.pb.h"
#include<iostream>


int main()
{
    contacts::contact conn;
    conn.set_sn(23000);
    conn.set_name("小虎");
    conn.set_score(88);
    //序列化
    std::string str = conn.SerializeAsString();

    //反序列化
    contacts::contact stu;
    bool ret = stu.ParseFromString(str);
    if(ret==false)
    {
        std::cout<<"序列化反序列化失败"<<std::endl;
        return -1;
    }
    std::cout<<stu.sn()<<std::endl;
    std::cout<<stu.name()<<std::endl;
    std::cout<<stu.score()<<std::endl;
    return 0;
}

2 Muduo库

Muduo 由陈硕大佬开发,是一个基于非阻塞 IO 和事件驱动的 C++高并发 TCP 网络编
程库。 它是一款基于主从 Reactor 模型的网络库,其使用的线程模型是 one loop one
thread, 所谓 one loop one thread 指的是:

  • 一个线程只能有一个事件循环(EventLoop), 用于响应计时器和 IO 事件

  • 一个文件描述符只能由一个线程进行读写,换句话说就是一个 TCP 连接必须归属
    于某个 EventLoop 管理

(1) 项目中用到库中的类接口介绍

  • muduo::net::TcpServer 类基础介绍
cpp 复制代码
typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
typedef std::function<void(const TcpConnectionPtr&)> ConnectionCallback;
typedef std::function<void(const TcpConnectionPtr&,Buffer*,Timestamp)> MessageCallback;
class InetAddress : public muduo::copyable
{ 
public:
 InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);
};
class TcpServer : noncopyable
{ 
public:
	 enum Option
	 { 
	 kNoReusePort,
	 kReusePort,
	 };
	 TcpServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg,Option option = kNoReusePort);
	 void setThreadNum(int numThreads);
	 void start();
	 /// 当一个新连接建立成功的时候被调用
	 void setConnectionCallback(const ConnectionCallback& cb)
	 { connectionCallback_ = cb; }
	 /// 消息的业务处理回调函数---这是收到新连接消息的时候被调用的函数
	 void setMessageCallback(const MessageCallback& cb)
	 { messageCallback_ = cb; }
};

(2)TcpServer的构造函数:

这个是一个服务器的架构,有了这个类就可以实现服务器的架构了

  • EventLoop* loop

  • InetAddress& listenAddr

    地址信息,里面包含了俩个成员:ip,port
    ip就是我们要监听的ip地址,port就是我们监听的端口

  • string& nameArg

    服务器名称(标识作用)

  • Option option = kNoReusePort

    端口重用功能。解释:tcp一个链接如果主动断开链接了,那么就会进入一个timewait状态,这时候是不能立即复用这个端口的

(3)函数接口setThreadNum()

moduo库是一个基于主从reactor模型的高性能服务器框架

  • reactor模型:基于事件触发的模型(基于epoll进行IO事件监控)

  • 主从reactor模型:进行层次划分 主/从
    主reactor只对新建链接事件进行监控 (保证不受IO操作影响 实现高校获取链接得效果)
    从reactor :针对新建链接进行IO事件监控(进行IO操作)
    主从reactor :必然是多个执行流的并发模式---one thread one loop 一个事件监控占据一个线程,进行事件监控


这个从属reactor是可以有多个的
而这个setThreadNum() 这个函数是设置线程数量的

(4)接口函数 start()

开始套接字监听功能

(5)俩个回调函数

  • setConnectionCallback
    当一个新连接建立成功的时候被调用

  • setMessageCallback
    消息的业务处理回调函数---这是收到新连接消息的时候被调用的函数

(6)EventLoop类

一个循环一个线程: one Loop one thread

  • 事件、处理监控循环

代码:

cpp 复制代码
class EventLoop : noncopyable
{ 
public:
	/// Loops forever.
	 /// Must be called in the same thread as creation of the object.
	 void loop();
	 /// Quits loop.
	 /// This is not 100% thread safe, if you call through a raw 
	pointer,
	 /// better to call through shared_ptr<EventLoop> for 100% 
	safety.
	 void quit();
	 TimerId runAt(Timestamp time, TimerCallback cb);
	 /// Runs callback after @c delay seconds.
	 /// Safe to call from other threads.
	 TimerId runAfter(double delay, TimerCallback cb);
	 /// Runs callback every @c interval seconds.
	 /// Safe to call from other threads.
	 TimerId runEvery(double interval, TimerCallback cb);
	 /// Cancels the timer.
	 /// Safe to call from other threads.
	 void cancel(TimerId timerId);
private:
	 std::atomic<bool> quit_;
	 std::unique_ptr<Poller> poller_;
	 mutable MutexLock mutex_;
	 std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);
};

在这个EventLoop中封装了socket监听套接字 ,和对于事件的处理操作

所以有了这个EvenetLoop这个类才能进行事件监控事件的处理

  • 函数接口loop()
    这个函数是开始事件监听/处理的循环是一个死循环

(7) TcpConnect类

代码:

cpp 复制代码
class TcpConnection : noncopyable,
 public 
std::enable_shared_from_this<TcpConnection>
{ 
public:
 /// Constructs a TcpConnection with a connected sockfd
 ///
 /// User should not create this object.
 TcpConnection(EventLoop* loop,
	 const string& name,
	 int sockfd,
	 const InetAddress& localAddr,
	 const InetAddress& peerAddr);
	 bool connected() const { return state_ == kConnected; }
	 bool disconnected() const { return state_ == kDisconnected; }
	 void send(string&& message); // C++11
	 void send(const void* message, int len);
	 void send(const StringPiece& message);
	 // void send(Buffer&& message); // C++11
	 void send(Buffer* message); // this one will swap data
	 void shutdown(); // NOT thread safe, no simultaneous calling
	 void setContext(const boost::any& context)
	 { context_ = context; }
	 const boost::any& getContext() const
	 { return context_; }
	 boost::any* getMutableContext()
	 { return &context_; }
	 void setConnectionCallback(const ConnectionCallback& cb)
	 { connectionCallback_ = cb; }
	 void setMessageCallback(const MessageCallback& cb)
	 { messageCallback_ = cb; }
private:
	 enum StateE { kDisconnected, kConnecting, kConnected, 
	kDisconnecting };
	 EventLoop* loop_;
	 ConnectionCallback connectionCallback_;
	 MessageCallback messageCallback_;
	 WriteCompleteCallback writeCompleteCallback_;
	 boost::any context_;
};
  • void send(Buffer&& message)
    发送数据
  • shutdown()
    关闭链接

Buffer类型

cpp 复制代码
class Buffer : public muduo::copyable
{ 
public:
	 static const size_t kCheapPrepend = 8;
	 static const size_t kInitialSize = 1024;
	 explicit Buffer(size_t initialSize = kInitialSize)
	 : buffer_(kCheapPrepend + initialSize),
	 readerIndex_(kCheapPrepend),
	 writerIndex_(kCheapPrepend);
	 void swap(Buffer& rhs)
	 size_t readableBytes() const
	 size_t writableBytes() const
	 const char* peek() const
	 const char* findEOL() const
	 const char* findEOL(const char* start) const
	 void retrieve(size_t len)
	 void retrieveInt64()
	 void retrieveInt32()
	 void retrieveInt16()
	 void retrieveInt8()
	 string retrieveAllAsString()
	 string retrieveAsString(size_t len)
	 void append(const StringPiece& str)
	 void append(const char* /*restrict*/ data, size_t len)
	 void append(const void* /*restrict*/ data, size_t len)
	 char* beginWrite()
	 const char* beginWrite() const
	 void hasWritten(size_t len)
	 void appendInt64(int64_t x)
	 void appendInt32(int32_t x)
	 void appendInt16(int16_t x)
	 void appendInt8(int8_t x)
	 int64_t readInt64()
	 int32_t readInt32()
	 int16_t readInt16()
	 int8_t readInt8()
	 int64_t peekInt64() const
	 int32_t peekInt32() const
	 int16_t peekInt16() const
	 int8_t peekInt8() const
	 void prependInt64(int64_t x)
	 void prependInt32(int32_t x)
	 void prependInt16(int16_t x)
	 void prependInt8(int8_t x)
	 void prepend(const void* /*restrict*/ data, size_t len)
	private:
	 std::vector<char> buffer_;
	 size_t readerIndex_;
	 size_t writerIndex_;
	 static const char kCRLF[];
	};
  • string retrieveAllAsString()
  • string retrieveAsString(size_t len)

这俩个函数的作用
获取缓冲区中的数据放到String当中返回


(7)搭建TCP服务器demo

代码实现一个翻译器服务器

cpp 复制代码
#include"./include/muduo/net/TcpServer.h"
#include"./include/muduo/net/EventLoop.h"
#include"./include/muduo/net/TcpConnection.h"
#include<functional>
#include<iostream>
#include<unordered_map>


class TranslateServer
{
    public:
        TranslateServer(int port)
            :_server(&_loop,muduo::net::InetAddress("0.0.0.0",port),"TranslateServer",muduo::net::TcpServer::kReusePort)
            {
                //std::bind 函数 适配器
                auto func=std::bind(&TranslateServer::onConnect,this,std::placeholders::_1);
                _server.setConnectionCallback(func);//绑定的回调函数
                _server.setMessageCallback(std::bind(&TranslateServer::OnMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));//事件处理回调函数
            }
        //启动
        void start()
        {
            _server.start();//事件监听
            _loop.loop();//事件监控,这是一个阻塞接口
        }
        
    private:
        void onConnect(const muduo::net::TcpConnectionPtr& conn)
        {
            if(conn->connected()==true)
            {
                std::cout<<"链接建立成功!!"<<std::endl;
            }
            else
            {
                std::cout<<"链接关闭!!"<<std::endl;
            }
        }
        std::string translate(const std::string& str)
        {
            //返回值不用引用,会走移动构造
            std::unordered_map<std::string,std::string> dict={
                {"python","你好"},{"c++","不会"},{"C","不熟"},{"JAVA","输啦"}
            };
            auto it=dict.find(str);
            if(it==dict.end())
            {
                return "不知道";
            }
            else
            {
                return it->second;
            }
        }
        void OnMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buf,muduo::Timestamp)
        {
            std::unordered_map<std::string,std::string> dict={
                {"hello","你好"},{"c++","不会"},{"C","不熟"},{"JAVA","输啦"}
            };
            //1.buf中获取数据
            std::string str=buf->retrieveAllAsString();
            //翻译
            std::string result=translate(str);
            //响应结果
            conn->send(result);
        }
    private:
        muduo::net::EventLoop _loop;
        muduo::net::TcpServer _server;

};


int main()
{
    TranslateServer server(8085);
    server.start();
}

注意:

lib包含了muduo库

include包含了muduo库中的头文件

(8)编译程序

  • -L 指定muduo库对应的库的路径
  • -l 指定库名称
  • -I 指定库路径

makefile 程序

bash 复制代码
server:mq_server.cpp
	g++ -o $@ $^ -std=c++11 -I./include  -L./lib  -lmuduo_net -lmuduo_base -pthread
.PHONY:clean
clean:
	rm -rf server

结果验证:

采用netstat查看进程

前提:先运行服务器

bash 复制代码
netstat -anptu | grep 8085

总结:

_server对象主要用于设置
新连接链接的回调函数:onConnect()
事件处理回调函数:onMessage()
服务器启动:start();

_loop是epool事件监控
会进行对描述符进行事件监控,触发事件后进行io处理


TcpClient类

cpp 复制代码
class TcpClient : noncopyable
{ 
public:
 // TcpClient(EventLoop* loop);
 // TcpClient(EventLoop* loop, const string& host, uint16_t 
port);
 TcpClient(EventLoop* loop,
 const InetAddress& serverAddr,
 const string& nameArg);
 ~TcpClient(); // force out-line dtor, for std::unique_ptr 
members.
 void connect();//连接服务器
 void disconnect();//关闭连接
 void stop();
 //获取客户端对应的通信连接 Connection 对象的接口,发起 connect 后,有可能还没有连接建立成功
 TcpConnectionPtr connection() const
 { 
 MutexLockGuard lock(mutex_);
 return connection_;
 } 
 /// 连接服务器成功时的回调函数
 void setConnectionCallback(ConnectionCallback cb)
 { connectionCallback_ = std::move(cb); }
 /// 收到服务器发送的消息时的回调函数
 void setMessageCallback(MessageCallback cb)
 { messageCallback_ = std::move(cb); }
private:
 EventLoop* loop_;
 ConnectionCallback connectionCallback_;
 MessageCallback messageCallback_;
 WriteCompleteCallback writeCompleteCallback_;
 TcpConnectionPtr connection_ GUARDED_BY(mutex_);
};
/*
需要注意的是,因为 muduo 库不管是服务端还是客户端都是异步操作,
对于客户端来说如果我们在连接还没有完全建立成功的时候发送数据,这是不被允
许的。
因此我们可以使用内置的 CountDownLatch 类进行同步控制
*/
class CountDownLatch : noncopyable
{ 
public:
 explicit CountDownLatch(int count);
 void wait(){
 MutexLockGuard lock(mutex_);
 while (count_ > 0)
 { 
 condition_.wait();
  }
 } 
 void countDown(){
	 MutexLockGuard lock(mutex_);
	 --count_;
	 if (count_ == 0)
	 { 
	 condition_.notifyAll();
	 } 
 } 
 int getCount() const;
private:
 mutable MutexLock mutex_;
 Condition condition_ GUARDED_BY(mutex_);
 int count_ GUARDED_BY(mutex_);
};

构造函数

TcpServer类很像

  • EventLoop* loop
  • inetAddress& serverAddr
  • string& nameArg

Connect

void connect()

作用:链接服务器

void disconnect()

作用:关闭链接

Connection

TcpConnectionPtr connection() const

作用:获取链接信息

回调函数

void setConnectionCallback(connectionCallback cb)

链接服务器成功时候的回调函数

void setMessageCallback(MessageCallback cb)

收到服务器的消息进行触发

CountDownLatch 类

成员:

条件变量和互斥锁

  • MutexLock mutex_;
  • Condition condition_;

成员函数:

  • wait()
  • countDown()

这俩个函数:
wait

分别是等待客户端链接服务器

countDownd

等待客户端连接完成服务器之后进行唤醒

(9)搭建Client客户端demo

cpp 复制代码
#include"./include/muduo/net/TcpClient.h"
#include"./include/muduo/net/EventLoopThread.h"
#include"./include/muduo/net/TcpConnection.h"
#include"./include/muduo/base/CountDownLatch.h"
#include<functional>
#include<iostream>



class TranslateClient
{
    public:
        TranslateClient(const std::string &sip,int sport)
        :_latch(1),
        _client(_loopthread.startLoop(),muduo::net::InetAddress(sip,sport),"TranslateClient")
        {
            _client.setConnectionCallback(std::bind(&TranslateClient::onConnect,this,std::placeholders::_1));
            _client.setMessageCallback(std::bind(&TranslateClient::OnMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));//事件处理回调函数
        }
        void connect()
        {
            _client.connect();//这个链接会立即返回
            _latch.wait();//进行阻塞等待链接建立成功
        }
        bool send(const std::string& msg)
        {
            if(_conn->connected())
            {
                _conn->send(msg);
                return true;
            }
            return false;
        }
    private:
        void onConnect(const muduo::net::TcpConnectionPtr& conn)
        {
            //链接建立成功
            if(conn->connected())
            {
                _latch.countDown();
                _conn=conn;
            }
            else//链接断开时候
            {
                _conn.reset();
                std::cout<<"链接断开了"<<std::endl;
            }
        }

        void OnMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buf,muduo::Timestamp)
        {
            std::cout<<buf->retrieveAllAsString()<<std::endl;
        }
    private:
        muduo::CountDownLatch _latch;
        muduo::net::EventLoopThread _loopthread;
        muduo::net::TcpClient _client;
        muduo::net::TcpConnectionPtr _conn;
};





int main()
{
    TranslateClient client("127.0.0.1",8085);
    client.connect();
    while(1)
    {
        std::string buf;
        std::cin>>buf;
        client.send(buf);
    }
}

三、实现一个基于Protobuf的通信协议客户端与服务器

之前实现的简单客户端和服务器的搭建是没有考虑到协议层
只是针对string对象,进行接收和发送,没有考虑到粘包的问题

对于粘包问题是要自己设置网络协议的,所以这里使用muduo库中对Protobuf的接口的使用 ,在这里不设计网络协议,用到库的网络协议

cpp 复制代码
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
        typedef std::shared_ptr<bit::TranslateResponse> TranslateResponsePtr;
        typedef std::shared_ptr<bit::AddResponse> AddResponsePtr;
        Client(const std::string &sip,int prot):_latch(1),_client(_loop.startLoop(),muduo::net::InetAddress(sip,prot),"Client"),
        _dispatcher(std::bind(&Client::onUnknownMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)),
        _codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
        {
            //注册请求处理函数
            _dispatcher.registerMessageCallback<bit::TranslateResponse>(std::bind(&Client::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback<bit::AddResponse>(std::bind(&Client::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _client.setConnectionCallback(std::bind(&Client::onConnection,this,std::placeholders::_1));
        }

(1)客户端

cpp 复制代码
#include<iostream>
#include"muduo/proto/dispatcher.h"
#include"muduo/proto/codec.h"
#include"request.pb.h"
#include"muduo/base/Logging.h"
#include"muduo/base/Mutex.h"
#include"../include/muduo/net/TcpClient.h"
#include"../include/muduo/net/EventLoopThread.h"
#include"../include/muduo/net/InetAddress.h"
#include"../include/muduo/net/TcpConnection.h"
#include"muduo/proto/google-inl.h"
#include"muduo/base/CountDownLatch.h"



class Client
{
    public:
        typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
        typedef std::shared_ptr<request::TranslateRequest> TranslateRequestPtr;
        typedef std::shared_ptr<request::TranslateResponse> TranslateResponsePtr;
        typedef std::shared_ptr<request::AddRequest> AddRequestPtr;
        typedef std::shared_ptr<request::AddResponse> AddResponsePtr;
        Client(const std::string& str,int port)
            :_latch(1),
             _client(_loop.startLoop(),muduo::net::InetAddress(str,port),"Client"),
             _dispatcher(std::bind(&Client::UnkonwMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)),
             _codec(std::bind(&ProtobufDispatcher::onProtobufMessage,&_dispatcher,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3))
            {
                std::cout<<"进入初始化"<<std::endl;
                _dispatcher.registerMessageCallback<request::TranslateResponse>(std::bind(&Client::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
                _dispatcher.registerMessageCallback<request::AddResponse>(std::bind(&Client::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
                _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
                _client.setConnectionCallback(std::bind(&Client::onConnection,this,std::placeholders::_1));
            }
        void connect()
        {
            _client.connect();
            _latch.wait();
        }
        void Translate(const std::string & msg)
        {
            TranslateRequestPtr req=std::make_shared<request::TranslateRequest>();
            req->set_msg(msg);
            send(req);
        }
        void Add(int num1,int num2)
        {
            AddRequestPtr req=std::make_shared<request::AddRequest>();
            req->set_num1(num1);
            req->set_num2(num2);
            send(req);
        }
        
    private:
        bool send(const MessagePtr& req)
        {
            if(_conn &&_conn->connected())
            {
                _codec.send(_conn,*req);
                return true;
            }
            else
            {
                return false;
            }
        }
        void UnkonwMessage(const muduo::net::TcpConnectionPtr& conn,const MessagePtr& message,muduo::Timestamp)
        {
            LOG_INFO<<"onkonwmessage: "<<message->GetTypeName();
            // conn->shutdown();
        }
        void onConnection(const muduo::net::TcpConnectionPtr& conn)
        {
            if(conn->connected())
            {
                _latch.countDown();
                _conn=conn;
                LOG_INFO<<"链接成功";
            }
            else
            {
                _conn.reset();
                LOG_INFO<<"链接关闭";
            }
        }
        void onAdd(const muduo::net::TcpConnectionPtr& conn,const AddResponsePtr& message,muduo::Timestamp)
        {
            std::cout<<"运算结果: "<<message->result()<<std::endl;
        }
        void onTranslate(const muduo::net::TcpConnectionPtr& conn,const TranslateResponsePtr& message,muduo::Timestamp)
        {
            std::cout<<"翻译结果: "<<message->msg()<<std::endl;
        }
    private:
        ProtobufDispatcher _dispatcher;//请求分发器
        ProtobufCodec _codec;//协议处理
        muduo::CountDownLatch _latch;//同步
        muduo::net::EventLoopThread _loop;//异步循环处理线程
        muduo::net::TcpClient _client;//客户端
        muduo::net::TcpConnectionPtr _conn;//客户链接
};




int main()
{
    Client client("127.0.0.1",8085);
    client.connect();
    client.Translate("hello");
    client.Add(11,33);
    sleep(1);
    return 0;
}

(2)服务器

cpp 复制代码
#include<iostream>
#include"request.pb.h"
#include"muduo/base/Logging.h"
#include"muduo/base/Mutex.h"
#include"../include/muduo/net/TcpServer.h"
#include"../include/muduo/net/EventLoop.h"
#include"../include/muduo/net/InetAddress.h"
#include"../include/muduo/net/TcpConnection.h"
#include"../include/muduo/proto/codec.h"
#include"../include/muduo/proto/dispatcher.h"
#include<string>
#include<map>

class TcpServer
{
    public:
        //智能指针
        typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
        typedef std::shared_ptr<request::TranslateRequest> TranslateRequestPtr;
        typedef std::shared_ptr<request::TranslateResponse> TranslateResponsePtr;
        typedef std::shared_ptr<request::AddRequest> AddRequestPtr;
        typedef std::shared_ptr<request::AddResponse> AddResponsePtr;
        TcpServer(int port)
            :_server(&_loop,muduo::net::InetAddress("0.0.0.0",port),"TcpServer",muduo::net::TcpServer::kReusePort),
             _dispatcher(std::bind(&TcpServer::UnkonwMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)),
             _codec(std::bind(&ProtobufDispatcher::onProtobufMessage,&_dispatcher,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3))
            {
                //注册请求处理函数
                _dispatcher.registerMessageCallback<request::TranslateRequest>(std::bind(&TcpServer::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
                _dispatcher.registerMessageCallback<request::AddRequest>(std::bind(&TcpServer::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
                _server.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
                _server.setConnectionCallback(std::bind(&TcpServer::onConnection,this,std::placeholders::_1));
            }
        void connect()
        {
            _server.start();
            _loop.loop();
        }
    private:
            std::string translate(const std::string& str)
            {
                //返回值不用引用,会走移动构造
                std::unordered_map<std::string,std::string> dict={
                    {"python","你好"},{"c++","不会"},{"C","不熟"},{"JAVA","输啦"}
                };
                auto it=dict.find(str);
                if(it==dict.end())
                {
                    return "不知道";
                }
                else
                {
                    return it->second;
                }
            }
            void onConnection(const muduo::net::TcpConnectionPtr& conn)
            {
                if(conn->connected())
                {
                    LOG_INFO<<"链接成功";
                }
                else
                {
                    LOG_INFO<<"链接关闭";
                }
            }
            void UnkonwMessage(const muduo::net::TcpConnectionPtr& conn,const MessagePtr& message,muduo::Timestamp)
            {
                LOG_INFO<<"onkonwmessage: "<<message->GetTypeName();
                conn->shutdown();
            }
            void onTranslate(const muduo::net::TcpConnectionPtr& conn,const TranslateRequestPtr& message,muduo::Timestamp)
            {
                //从message中获取数据
                std::string str=message->msg();
                //翻译
                std::string rsp_msg=translate(str);
                //响应
                request::TranslateResponse resp;
                resp.set_msg(rsp_msg);
                //发送
                //这里需要专门通过ProtobufCodec进行发送
                _codec.send(conn,resp);
            }
            void onAdd(const muduo::net::TcpConnectionPtr& conn,const AddRequestPtr& message,muduo::Timestamp)
            {
                int num1=message->num1();
                int num2=message->num2();
                int result=num1+num2;
                request::AddResponse resp;
                resp.set_result(result);
                _codec.send(conn,resp);
            }

    private:
        
        muduo::net::EventLoop _loop;
        muduo::net::TcpServer _server;
        ProtobufDispatcher _dispatcher;//请求分发器
        ProtobufCodec _codec;//协议处理器,对请求数据进行protobuf处理
};



int main()
{
    TcpServer s(8085);
    s.connect();
    return 0;
}

效果:
服务器处理消息返回响应交给客户端进行打印


四、项目核心支撑组件

(1)SQLite3

SQLite 是一个进程内的轻量级数据库,它实现了自给自足的、无服务器的、零配置的、

事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库不一样,

我们不需要在系统中配置。像其他数据库,SQLite 引擎不是一个独立的进程,可以按

应用程序需求进行静态或动态连接,SQLite 直接访问其存储文件

为什么要用SQLite

• 不需要一个单独的服务器进程或操作的系统(无服务器的)

• SQLite 不需要配置

• 一个完整的 SQLite 数据库是存储在一个单一的跨平台的磁盘文件

• SQLite 是非常小 的,是轻量级的,完全配置时小于 400KiB,省略可选功能配置时

小于 250KiB

• SQLite 是自给自足的,这意味着不需要任何外部的依赖

• SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问

• SQLite 支持 SQL92(SQL2)标准的大多数查询语言的功能

• SQLite 使用 ANSI-C 编写的,并提供了简单和易于使用的 API

• SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32,

WinCE, WinRT)中运行

SQLlite API介绍

创建/打开数据库文件,并返回操作句柄

  • 方式1
c 复制代码
int sqlite3_open(const char *filename, sqlite3 **ppDb)
  • 方式2
c 复制代码
int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int 
flags, const char *zVfs );

flag:
SQLITE_OPEN_READWRITE -- 以可读可写方式打开数据库文件
SQLITE_OPEN_CREATE -- 不存在数据库文件则创建
SQLITE_OPEN_NOMUTEX --多线程模式,只要不同的线程使用不同的连接即可保证线程安全
SQLITE_OPEN_FULLMUTEX--串行化模式

执行语句API

c 复制代码
int sqlite3_exec(sqlite3*, char *sql, int 
(*callback)(void*,int,char**,char**), 
 void* arg, char **err)
 int (*callback)(void*,int,char**,char**)
 void* : 是设置的在回调时传入的 arg 参数
 int:一行中数据的列数
 char**:存储一行数据的字符指针数组
 char**:每一列的字段名称

返回值:

返回0,如果返回非0 ,就会触发ABORT

简单实现sqlite类

cpp 复制代码
/*

封装实现一个sqliteHelper类,提供简单的sqlite数据库操作接口,完成数据的基础增删查改操作。
    1.创建/打开数据库文件
    2.针对打开的数据库执行操作
        1.表的操作
        2.数据的操作
    3.关闭数据库
*/
#include<sqlite3.h>
#include<iostream>
#include<vector>
#include<string>

class SqilteHelper{
    public:
        typedef int(*SqliteCallback)(void*,int,char**,char**);//函数指类型
        SqilteHelper(const std::string &dbfile):_dbfile(dbfile),_handler(nullptr)
        {

        }
        bool open(int safe_leve=SQLITE_OPEN_FULLMUTEX)//设置安全等级为串行线程安全
        {
            //打开数据库
            int ret=sqlite3_open_v2(_dbfile.c_str(),&_handler,SQLITE_OPEN_CREATE|safe_leve|SQLITE_OPEN_READWRITE,nullptr);
            if(ret!=SQLITE_OK)
            {
                std::cout<<"创建打开sqlite3数据库失败: "<<std::endl;
                std::cout<<sqlite3_errmsg(_handler)<<std::endl;
                return false;
            }
            else
            {
                return true;
            }
        }
        bool exec(const std::string &sql,SqliteCallback cb,void *arg)
        {
            //int sqlite3_exec(sqlite3*,char*sql,int(*callback)(void*,int,char**,char**),void* arg,char **err)
            int ret=sqlite3_exec(_handler,sql.c_str(),cb,arg,nullptr);
            if(ret!=SQLITE_OK)
            {
                std::cout<<sql<<std::endl;
                std::cout<<"执行语句失败"<<std::endl;
                std::cout<<sqlite3_errmsg(_handler)<<std::endl;
                return false;
            }
            return true;
        }
        bool close()
        {
            if(_handler)
            {
                sqlite3_close_v2(_handler);
                return true;
            }
            return false;
        }
    private:
        std::string _dbfile;
        sqlite3* _handler;
};

sqlite3 测试

c 复制代码
int main()
{
    SqilteHelper helper("./test.db");
    //1.创建/打开一个文件
    assert(helper.open());
    //2.创建表,学生信息: 学号,姓名,年龄
    const char* ct="create table if not exists student(sn int primary key,name varchar(32),age int)";
    assert(helper.exec(ct,nullptr,nullptr));
    //3.新增数据,修改,删除,查询,关闭数据库
    // const char* select_sql="select *from student";
    // assert(helper.exec(select_sql,nullptr,nullptr));
    // const char* insert_sql="insert into student values(2,'陈向',20),(3,'小红',21),(1,'小明',11)";
    // assert(helper.exec(insert_sql,nullptr,nullptr));

    const char* update_sql="update student set name='陈小明' where sn=1";
    assert(helper.exec(update_sql,nullptr,nullptr));

    // const char* delete_sql="delete from student where sn=1";
    // assert(helper.exec(delete_sql,nullptr,nullptr));
    // const char* select_sql="select name from student";
    // std::vector<std::string> arr;

    // assert(helper.exec(select_sql,select_stu_callback,&arr));
    // for(auto &name:arr){ std::cout<<name<<std::endl;}
    //4.关闭数据库
    assert(helper.close());

    return 0;
}

ubuntu中sqlite指令使用

打开指定库

bash 复制代码
sqlite3  库  如:sqlite3 test.db

查找表

bash 复制代码
.tables

打开指定表

bash 复制代码
select* from 表名称

(2)C++异步操作实现线程池

std::future

std::future 是 C++11 标准库中的一个模板类,它表示一个异步操作的结果。当我们在
多线程编程中使用异步任务时,std::future 可以帮助我们在需要的时候获取任务的执行
结果。std::future 的一个重要特性是能够阻塞当前线程,直到异步操作完成,从而确保
我们在获取结果时不会遇到未完成的操作。

应用场景

  • 异步任务: 当我们需要在后台执行一些耗时操作时,如网络请求或计算密集型任务等,std::future 可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率

  • 并发控制: 在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用 std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作

async函数

async 作用:异步调用一个函数

async可以传入一个函数,和函数的参数 这样就会异步调用这个函数

策略:
launch::async 异步执行
launch::deferred 占据当前线程,进行执行任务

返回值:

async返回值是一个future对象

cpp 复制代码
#include <iostream>
#include <future> // 必须包含这个头文件
#include <string>

// 模拟你的翻译函数(会在后台线程运行)
std::string translate_async(const std::string& english)
{
    // 模拟数据库查询,耗时2秒
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    // 返回翻译结果
    if(english == "hello") return "你好";
    if(english == "world") return "世界";
    return "不知道";
}

int main()
{
    std::cout << "开始发起翻译请求..." << std::endl;

    // ✅ 核心1:用 std::async 启动异步任务,返回一个 future
    // 这就像你点了外卖,拿到了一个"取餐码"
    std::future<std::string> fut = std::async(std::launch::async, translate_async, "hello");

    // ✅ 这里可以干别的事!不会阻塞!
    std::cout << "我在等翻译的同时,正在写代码..." << std::endl;

    // ✅ 核心2:用 fut.get() 获取结果
    // 这就像你凭取餐码去拿外卖
    // 如果翻译还没完成,get() 会阻塞等待(就像 latch.wait())
    std::string result = fut.get();

    std::cout << "翻译结果:" << result << std::endl;
    return 0;
}

俩种政策的区别

launch::async

cpp 复制代码
#include<iostream>
#include<thread>
#include<future>
#include<chrono>

int Add(int num1,int num2)
{
    std::cout<<"加法器111"<<std::endl;
    std::cout<<"加法器222"<<std::endl;
    return num1+num2;
}
int main()
{
    std::cout<<"-------------1--------------------------\n";
    std::future<int> fut=std::async(std::launch::async,Add,11,22);
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout<<"-------------2--------------------------\n";
    int sum=fut.get();//等待异步获取结果主线程才能继续执行
    std::cout<<"-------------3--------------------------\n";
    std::cout<<sum<<std::endl;
    return 0;
}

如果是异步执行那么,主线程在休眠的时候,异步会执行Add()

加法器111 和 加法器222 出现在 打印日志2的上面

launch::deferred

cpp 复制代码
#include<iostream>
#include<thread>
#include<future>
#include<chrono>

int Add(int num1,int num2)
{
    std::cout<<"加法器111"<<std::endl;
    std::cout<<"加法器222"<<std::endl;
    return num1+num2;
}
int main()
{
    std::cout<<"-------------1--------------------------\n";
    std::future<int> fut=std::async(std::launch::async,Add,11,22);
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout<<"-------------2--------------------------\n";
    int sum=fut.get();//等待异步获取结果主线程才能继续执行
    std::cout<<"-------------3--------------------------\n";
    std::cout<<sum<<std::endl;
    return 0;
}

加法器111 和 加法器222 出现在 打印日志2的下面

promise 函数获取异步结果


Promise 模板类

  • get_future
    返回一个future对象 ,这时候这个promise和future 对象之间就具有同步关系
cpp 复制代码
#include<iostream>
#include<thread>
#include<future>
int Add(int num1,int num2,std::promise<int>& prom)
{
	prom.set_value(num1+num2);
	return num1+num2;
}
int main()
{
	std::promise<int> pro;
	std::future<int> fu=pro.get_future();//这时候promise和future就具有同步了关联关系了
	//异步执行
	std::thread thre(Add,11,22,std::ref(pro));
	std::cout<<"============1==============\n"<<std::endl;
	int res=fu.get();//获取异步结果
	std::cout<<res<<std::endl;
	thre.join();//等待线程退出
	return 0;
}

这里就说明了,promise与fu建立了同步关系 ,修改了promise的内容,fu立马能同步信息!!!

packaged_task

std::packaged_task 就是将任务和 std::future 绑定在一起的模板,是一种对任务的封
装。我们可以通过 std::packaged_task 对象获取任务相关联的 std::future 对象,通过
调用 get_future()方法获得。

cpp 复制代码
#include<thread>
#include<future>
#include<iostream>
#include<chrono>
#include<memory>
int Add(int num1,int num2)
{
	std::this_thread::sleep_for(std::chrono::seconds(3));
	return num1+num2;
}
int main()
{
	//std::packageed_task<int(int,int)> pt(Add);
	//std::future<int> fu=pt.get_future();//这里也是通过共享状态进行同步
	//std::thread thr(task,11,22);//task可以当作一个个调用对象执行,但是他又不能完全当作一个函数去使用,像这样使用会报错!!
	//我们可以把task定义成为一个指针,传递到线程中,然后继续宁解引用执行,但是指针存在生命周期的风险,所以这里采用智能指针来进行管理
	std::shared_ptr<std::packaged_task<int(int,int)>> ptask=std::make_shared<std::packged_task<int(int,int)>>(Add);
	std::future<int> fu=ptask->get_future();
	std::thread([ptask](){
	(*ptask)(11,22)});
	int sum=fu.get();
	std::cout<<sum<,std::endl;
	thr.join();
	return 0;
}

线程池

cpp 复制代码
#include<iostream>
#include<functional>
#include<thread>
#include<memory>
#include<future>
#include<condition_variable>
#include<vector>


class ThreadPool
{
    public:
        using Functor=std::function<void(void)>;
        ThreadPool(int thr_count=1)
            :_stop(false)
        {
            for(int i=0;i<thr_count;i++)
            {
                _threads.emplace_back(&ThreadPool::entry,this);
            }
        }
        void stop()
        {
            if(_stop==true)
            {
                return ;
            }
            else
            {
                _stop=true;
                _cv.notify_all();
                for(auto& thread:_threads)
                {
                    thread.join();
                }
            }
        }
        ~ThreadPool()
        {
            stop();
        }
        //函数push传入的函数是表示
        template<typename F,typename ...Args>
        auto push(F&& func,Args&& ...args)->std::future<decltype(func(args...))> //这里我们传入的函数是packaged_task类型,这个类型会有一个返回值future,对于这个返回值我们要用到auto因为你不知道这个future<>返回地模板参数是什么,采用auto之后要搭配 类型推导才能编译decltype
        {
            //1.将函数分装成packaged_task
            using return_type=decltype(func(args...));
            auto func1=std::bind(std::forward<F>(func),std::forward<Args>(args)...);
            // std::packaged_task<return_type()> task(func1);
            auto task1=std::make_shared<std::packaged_task<return_type()>>(func1);
            std::future<return_type> fu=task1->get_future();
            {
                std::unique_lock<std::mutex> lock(_mutex);//加所
                _taskpools.push_back([task1](){(*task1)();});
                _cv.notify_one();//唤醒一个消费者
            }
            return fu;
        }
    private:
        void entry()//线程入口函数
        {
            while(!_stop)
            {
                std::vector<Functor> tmp_threadpool;
                {
                    //加锁
                    std::unique_lock<std::mutex> lock(_mutex);
                    //等待线程池不为空,或者_stop被置为
                    _cv.wait(lock,[this](){return _stop||!_taskpools.empty();});//等待锁,lambda是唤醒之后的行为
                    //获取
                    tmp_threadpool.swap(_taskpools);
                }
                for(auto& task:tmp_threadpool)
                {
                    task();
                }
            }
        }
    private:
        std::atomic<bool> _stop;
        std::mutex _mutex;
        std::condition_variable _cv;//条件变量
        std::vector<std::thread> _threads;//线程池
        std::vector<Functor> _taskpools;//任务池
};

五、 项目的框架设计

发布客户端----消息队列服务器----消费客户端

  • 一个生产者, 一个消费者

  • N个生产者,N个消费者

    AMQP(Advanced Message Queuing Protocol-高级消息队列协议,一个提供统一
    消息服务的应用层标准高级消息队列协议,为面向消息的中间件设计,使得遵从该规
    范的客户端应用和消息中间件服务器的全功能互操作成为可能)模型中,也就是消息中
    间件服务器 Broker 中,又存在以下概念:

  • 虚拟机 (VirtualHost) : 类似于 MySQL 的 "database", 是一个逻辑上的集合。一个

    BrokerServer 上可以存在多个 VirtualHost

  • 交换机 (Exchange) : 生产者把消息先发送到 BrokerExchange 上,再根据不同

    的规则, 把消息转发给不同的 Queue

  • 绑定 (Binding) : ExchangeQueue 之间的关联关系,Exchange 和 Queue 可以

    理解成 "多对多" 关系,使用一个关联表就可以把这两个概念联系起来


上述数据结构,既需要内存储存,也需要磁盘存储

  • 内存存储:方便使用
  • 硬盘存储:重启数据不丢失

总结: 要实现的内容

1:Borker消息队列服务器

2:消息发布客户端

3:消息订阅客户端


(1)Borkerserver核心API

1. 创建交换机 (exchangeDeclare)
2. 销毁交换机 (exchangeDelete)
3. 创建队列 (queueDeclare)
4. 销毁队列 (queueDelete)
5. 创建绑定 (queueBind)
6. 解除绑定 (queueUnbind)
7. 发布消息 (basicPublish)
8. 订阅消息 (basicConsume)
9. 确认消息 (basicAck)
10. 取消订阅 (basicCancel)

(2)交换机类型

对于 RabbitMQ 来说, 主要支持四种交换机类型:

• Direct

• Fanout

• Topic

• Header

其中 Header 这种方式比较复杂, 比较少见。常用的是前三种交换机类型,项目中也主

要实现这三种

• Direct: 生产者发送消息时, 直接指定被该交换机绑定的队列名

• Fanout: 生产者发送的消息会被复制到该交换机的所有队列中

• Topic: 绑定队列到交换机上时, 指定一个字符串为 bindingKey。发送消息指定一个

字符串为 routingKey 。当 routingKeybindingKey 满足一定的匹配条件的时候, 则把

消息投递到指定队列

(3)持久化

Exchange, Queue, Binding, Message 等数据都有持久化需求
当程序重启 / 主机重启, 保证上述内容不丢失

(4)网络通信API接口

生产者和消费者都是客户端程序, Broker 则是作为服务器,通过网络进行通信。

在网络通信的过程中, 客户端部分要提供对应的 api, 来实现对服务器的操作。

  1. 创建 Connection
  2. 关闭 Connection
  3. 创建 Channel
  4. 关闭 Channel
  5. 创建队列 (queueDeclare)
  6. 销毁队列 (queueDelete)
  7. 创建交换机 (exchangeDeclare)
  8. 销毁交换机 (exchangeDelete)
  9. 创建绑定 (queueBind)
  10. 解除绑定 (queueUnbind)
  11. 发布消息 (basicPublish)
  12. 订阅消息 (basicConsume)
  13. 确认消息 (basicAck)
  14. 取消订阅(basicCancel)

可以看到, 在 Broker 的基础上, 客户端还要增加 Connection 操作和 Channel 操作

Connection 对应一个 TCP 连接

Channel 则是 Connection 中的逻辑通道
一个 Connection 中可以包含多个 Channel 。Channel 和 Channel 之间的数据是独立

的,不会相互干扰。这样做主要是为了能够更好的复用 TCP 连接, 达到长连接的效果,
避免频繁的创建关闭 TCP 连接


六、项目实现

整体框架:

(1)Log日志功能

基础背景:

  • strftime()

    将一个时间结构体,按照指定的格式返回一个字符串

    往下拉之后我们可以看到对应的格式

    %H: 表示小时

    %M:分钟

    %S: 秒

  • localtime()

    会根据当前时间生成一个struct tm结构体方便我们格式化


cpp 复制代码
#include<iostream>
#include<ctime>
int mian()
{
	time_t t=time(nullptr);//获取当前时间
	struct tm* ptm=localtime(&t);
	char time_str[32];
	strftime(time_str,31,"%H:%M:%S",ptm);
	printf("[%s][%s:%d] Hello World!\n",time_str,__FILE__,__LINE__);
}

日志封装:

  • 宏函数
    定义日志等级
cpp 复制代码
#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL
#define LOG(lev_str,level,format,...)	{\
	if(level>=DEFAUT_LEVEL) {time_t t=time(nullptr);\
	struct tm* ptm=localtime(&t);\
	char time_str[32];\
	strftime(time_str,31,"%H:%M:%S",ptm);\
	printf("[%s][%s][%s:%d]\t" format "\n",lev_str,time_str,__FILE__,__LINE__,##__VA_ARGS__);\
	}\
}

#define DLOG(format,...) LOG("DBG",DBG_LEVEL,format,DBG_LEVEL)
#define ILOG(format,...) LOG("INF",INF_LEVEL,format,INF_LEVEL)
#define ELOG(format,...) LOG("ERR",ERR_LEVEL,format,ERR_LEVEL)
  • ##VA_ARGS
    宏函数要用到不定参,那么就要用到__VA_ARGS__。
    ##是以访我们没有传如数据,参数为空的情况

(2) 数据库封装

使用之前已经封装好的数据库

cpp 复制代码
#include<sqlite3.h>
#include<iostream>
#include<vector>
#include<string>
namespace cwz{
    class SqilteHelper{
        public:
            typedef int(*SqliteCallback)(void*,int,char**,char**);//函数指类型
            SqilteHelper(const std::string &dbfile):_dbfile(dbfile),_handler(nullptr)
            {

            }
            bool open(int safe_leve=SQLITE_OPEN_FULLMUTEX)//设置安全等级为串行线程安全
            {
                //打开数据库
                int ret=sqlite3_open_v2(_dbfile.c_str(),&_handler,SQLITE_OPEN_CREATE|safe_leve|SQLITE_OPEN_READWRITE,nullptr);
                if(ret!=SQLITE_OK)
                {
                    ELOG("打开数据库失败:%s",sqlite3_errmsg(_handler));
                    std::cout<<sqlite3_errmsg(_handler)<<std::endl;
                    return false;
                }
                else
                {
                    return true;
                }
            }
            bool exec(const std::string &sql,SqliteCallback cb,void *arg)
            {
                //int sqlite3_exec(sqlite3*,char*sql,int(*callback)(void*,int,char**,char**),void* arg,char **err)
                int ret=sqlite3_exec(_handler,sql.c_str(),cb,arg,nullptr);
                if(ret!=SQLITE_OK)
                {
                    std::cout<<sql<<std::endl;
                    ELOG("%s 执行语句失败:%s",sql.c_str(),sqlite3_errmsg(_handler));
                    return false;
                }
                return true;
            }
            bool close()
            {
                if(_handler)
                {
                    sqlite3_close_v2(_handler);
                    return true;
                }
                return false;
            }
        private:
            std::string _dbfile;
            sqlite3* _handler;
    };
}

(3)字符串分割功能

cpp 复制代码
 class StrHelper
    {
        public:
            static size_t split(const std::string& str,const std::string& sep,std::vector<std::string>& result)
            {
                //news.....music.#.pop  
                result.clear();
                size_t pos,idx=0;
                while(idx<str.size())
                {
                    pos=str.find(sep,idx);
                    if(pos==std::string::npos)
                    {
                        std::string tmp=str.substr(idx);
                        return result.size();
                    }
                    if(pos==idx) 
                    {
                        idx+=sep.size();
                        continue;
                    }
                    std::string tmp=str.substr(idx,pos-idx);
                    result.push_back(tmp);
                    idx=pos+sep.size();
                }
                return result.size();
            }
    };

(4)UUID唯一码

思想:

1.生成8个0-255之间的随机数

2.取出一个8字节序号编码

通过组合,组成一个16字节的数据,转换成16进制字符

  • random_device 随机数生成
    用这个random_device生成的随机数效率比较低
  • std::mt19937 (梅森旋转算法)
    C++11 标准库中最常用、性能极高、随机性优秀的伪随机数引擎 ,但是对于这个梅森旋转算法中需要提供seed种子

    这时候就可以用random_device进行生成种子。

  • std::uniform_int_distribution
    C++用来规定指定数据范围的模板类能把无规律大数字,均匀、无偏差地映射到 [a, b] 闭区间内的整数。

  • std::stringstream

    核心作用:把字符串当成「输入 / 输出流」来用,专门解决字符串拼接、类型转换、分割字符串、格式化读写等

定义16进制字符+序列号 ----------UUID

cpp 复制代码
class UUID{
public:
	static std::string uuid()
	{
	//生成种子
	std::random_device rd;
	//生成随机数
	std::mt9937_64 generator(rd());
	//规定范围
	std::uniform_int_distribution<int> distribution(0,256);
	//将生成的数字转换位16进制的数字字符
	std::stringstream ss;
	for(int i=0;i<8;i++)
	{
		ss<<std::setw(2)<<std::setfill('0')<<std::hex<<distribution(generator);
		if(i==3||i==5||i==7)
		{
			ss<"-";
		}
	}
	static std::atomic<size_t> seq(1);//定义一个原子类型整数,初始化唯一
	size_t num=seq.fetch_add();//每次拿去的时候就会递增,通过num来标记,这个num就是一个句号,size_t 是一个无符号长整型 8个字节 刚刚好作为一个序号插入到uuid值里面
	for(int i=7;i>=0;i--)
	{
		ss<<std::stew(2)<<std::setfill('0')<<std::hex<<((num>>i*8)&0xff);
		if(i==6) ss<<"-";
	}
	return ss.str();//返回字符串
  	}
}

(5)文件操作类

cpp 复制代码
class FileHelper{
    public:
        FileHelper(const std::string &filename):_filename(filename){}
    bool exists()
    {
        struct stat st;//这个是属性结构
        return (stat(_filename.c_str(),&st)==0);//通过这个stat函数用来判断文件存不存在,存在返回0,不存在返回-1   作用:会把文件的属性给到&st中,也就是获取文件的属性信息
    }
    size_t size()//文件大小,雷同,用stat的文件属性结构体返回size大小
    {
        struct stat sb;
        if(stat(_filename.c_str(),&sb)<0)
        {
            //文件不存在返回0
            return 0;
        }
        else
        {
            return sb.st_size;
        }
    }
    bool read(std::string &body)//读取数据
    {   
        //获取文件大小根据文件大小调整body空间
        size_t fsize=this->size();
        //不能直接body.c_str();,因为这样返回的是const的,所以才作用返回第一个字符的引用
        body.resize(fsize);
        return read(&body[0],0,fsize);

    }
    bool read(char *body,size_t offset,size_t len)//读取的形式,并不是读取全部内容
    {
        //1.打开文件,这里用到文件流来进行打开文件,第一个是名称,第二个是以什么样的方式打开,
        //以std::ios::binary(二进制),这个是很关键的,因为我们通常打开文件是以文本的形式打开进行操作,而文本模式,对文件进行读写的时候会有一定的偏差
        //如:\r \n 这种,他就会被特殊处理,比如你想写一个\n 那么文本的情况下,就会帮你自动补充\r\n,对于展示是没问题但是对于数据处理的时候是会右偏差的
        std::ifstream ifs(_filename,std::ios::binary|std::ios::in);
        if(ifs.is_open()==false)
        {
            ELOG("%s 文件打开失败",_filename.c_str());
            return false;
        }
        //2.跳转文件读写位置
        //seekg(偏移量,从什么位置开始跳)
        ifs.seekg(offset,std::ios::beg);
        //3.读写文件数据 参数1 读取到的地方  参数2 读取多少长度
        ifs.read(body,len);
        if(ifs.good()==false)
        {
            ELOG("%s 文件读取数据失败",_filename.c_str());
            ifs.close();
            return 0;
        }
        //4.关闭文件
        ifs.close();
        return true;

    }
    bool write(const std::string &body)//写入数据
    {
        return write(body.c_str(),0,body.size());
    }
    bool write(const char* body,size_t offset,int len)//重载,覆盖的写入
    {
        //1.打开文件,你要有读权限要不然你跳转不了,比如ofstream是跳转不了,你所指的地方的,所以用fstream 读/写权限都加上
        std::fstream ifs(_filename,std::ios::binary|std::ios::in|std::ios::out);
        if(ifs.is_open()==false)
        {
            ELOG("%s 文件打开失败",_filename.c_str());
            return false;
        }
        //2.跳转到文件指定位置
        ifs.seekg(offset,std::ios::beg);
        //3.写入数据
        ifs.write(body,len);
        if(ifs.good()==false)
        {
            ELOG("%s 文件读取数据失败",_filename.c_str());
            ifs.close();
            return 0;
        }
        //4.关闭文件
        ifs.close();
        return true;
    }
    bool rename(const std::string &nname)
    {
        //前面是old后面是new,::作用域位置符,代表我们使用的是全局的rename而不是自己的函数名rename
        return ::rename(_filename.c_str(),nname.c_str());
    }
    static bool createFile(const std::string &filename)//创建文件
    {
        std::ofstream ofs(filename,std::ios::binary|std::ios::out);
        if(ofs.is_open()==false)
        {
            ELOG("%s 文件创建失败",filename.c_str());
            return false;
        }
        ofs.close();
        return true;
    }
    static bool removeFile(const std::string &filename)//删除文件
    {
        return (::remove(filename.c_str())==0);
    }
    static bool createDirectory(const std::string &path)//创建目录
    {
        //再多级路径创建中,需要在第一个父级目录去创建
        //aaa/bbb/ccc  方法-> 创建ccc那么就需要一步一步来,先进行find("/")查找分隔符来进行创建父类目录,一步一步的创建
        size_t pos,idx=0;
        while(idx<path.size())
        {
            pos=path.find("/",idx);//从初始的时候去查找/
            //找到之后
            if(pos==std::string::npos)
            {
                //没找到,表明在单级目录
                //这里通过substr1来进行操作,会导致你创建的时候是为当下创建目录的,不考虑多级目录下的不同情况!!!
                //情况1:就当当只有你这个目录
                //情况2:这个目录是在之前的目录的前提下创建的目录
                // std::string substr1 =path.substr(idx,pos);//(idx)
                return (mkdir(path.c_str(),0775)==0);//所以要用到整体来创建目录
            }
            std::string substrpath=path.substr(0,pos);
            int ret=mkdir(substrpath.c_str(),0775);
            if(ret!=0&&errno!=EEXIST)
            {
                ELOG("创建目录 %s 失败:%s",substrpath.c_str(),strerror(errno));
                return false;
            }
            idx=pos+1;
        }
        return true;



    }
    static bool removeDirectory(const std::string &path)//删除目录
    {
        // rm -rf
        //接口system() 
        std::string cmd="rm -rf "+ path;
        return std::system(cmd.c_str())==0;

    }
    static std::string parentDirectory(const std::string &filename)//获取文件的父级目录
    {
        //aaa/bb/ccc/ddd/test.txt   查找最后的/然后在进行截取
        size_t pos=filename.find_last_of("/");
        if(pos==std::string::npos)
        {
            return "./";
        }
        std::string substr1=filename.substr(0,pos);
        return substr1;
    }
    private:
        std::string _filename;
};
}

(6)交换机数据管理

  • 定义交换机数据类
  1. 交换机名称
  2. 交换机类型
  3. 是否持久化标志
  4. 其他参数
  • 定义交换机数据持久化类(数据持久化的 sqlite3 数据库中)
  1. 创建/删除交换机数据表
  2. 新增交换机数据
  3. 移除交换机数据
  4. 查询所有交换机数据
  5. 查询指定交换机数据(根据名称)
  • 定义交换机数据管理类
  1. 声明交换机,并添加管理
  2. 删除交换机
  3. 获取指定交换机
  4. 销毁所有交换机数据

代码实现:

cpp 复制代码
namespace cwzmq{
    //定义我们的交换机类
    class Exchange{
        public:
            using ptr=std::shared_ptr<Exchange>;
            //名称
            std::string name;
            //交换机类型
            cwzmq::ExchangeType type;
            //交换机持久化标志
            bool durable;
            //交换机是否自动删除标志
            bool auto_delete;
            //其他参数
            google::protobuf::Map<std::string,std::string> args;
            //这边的args unordered_map类型会与protobuf自己的消息类中也会有一个unordered_map这个map,
            // 他们会导致冲突,也就是对应不上,所以会导致Exchange这里args持久化初始化有冲突
            Exchange(const std::string ename,
                cwzmq::ExchangeType etype,
                bool edurable,
                bool eauto_delete,
                const google::protobuf::Map<std::string,std::string> &eargs):name(ename),type(etype),durable(edurable),auto_delete(eauto_delete),args(eargs)
                {}
            Exchange(){}
            //args存储键值对,在我们存储数据库的时候,会组织一个格式字符串进行存储, 方式: key=val&key=val.....
            void setArgs(const std::string &str_args)//内部会进行解析str_args字符串,然后将内容存储到unordered_map args 成员中
            {
                // std::cout << "setArgs: " << str_args << std::endl;
                //存储格式:key=val&key=val..... 根据字符串进行分割
                std::vector<std::string> sub_str;
                cwzmq::StrHelper::split(str_args,"&",sub_str);
                for(auto &str:sub_str)
                {
                    size_t pos=str.find("=");
                    std::string key=str.substr(0,pos);
                    std::string val=str.substr(pos+1);
                    args[key]=val;
                }
            }
            //序列化,针对这个args内容序列化之后----key=val&key=val.....---,按照指定格式字符串进行返回
            std::string getArgs()
            {
                std::string result;
                for(auto start=args.begin();start!=args.end();++start)
                {
                    // std::cout << "start->first: " << start->first << "   " <<  "start->second:" << start->second << std::endl;
                    result += start->first+"="+start->second+"&";
                }
                return result;
            }
    
    };

    using ExchangeMap = std::unordered_map<std::string,Exchange::ptr>;
    //定义我们交换机数据持久化管理类--数据存储在sqlite3数据库中
    class ExchangeMapper{
        //在数据库中进行操作,所以必然是通过sqlite句柄进行操作
        public:
            //对内
            ExchangeMapper(const std::string &dbfile):_sql_helper(dbfile)
            {
                std::string path=cwzmq::FileHelper::parentDirectory(dbfile);//目录文件要我们自己创建,文件的话,sqlite会自己创建
                cwzmq::FileHelper::createDirectory(path);
                assert(_sql_helper.open());//创建数据库文件
                createtable();//创建表
            }
            void createtable()
            {
                #define CREATE_TABLE "create table if not exists exchange_table(name varchar(32) primary key,type int,durable int,auto_delete int,args varchar(128));"
                bool ret=_sql_helper.exec(CREATE_TABLE,nullptr,nullptr);
                if(ret==false){
                    DLOG("创建交换机数据表失败!!!");
                    abort();//退出
                }

            }
            void removetable()
            {
                #define DROP_TABLE "drop table if exists exchange_table;"
                bool ret =_sql_helper.exec(DROP_TABLE,nullptr,nullptr);
                if(ret==false){
                    DLOG("删除交换机数据表失败!!!");
                    abort();//退出
                }
            }
            bool insert(Exchange::ptr &exchange)//新增交换机
            {
                //C语言风格
                // #define INSERT_SQL "insert into exchange_table values('%s','%d','%d','%d','%s');"
                // std::string args_str=exchange->getArgs();
                // char sql_str[4096]={0};
                // //用sprintf进行格式化
                // sprintf(sql_str,INSERT_SQL,exchange->name,exchange->type,exchange->durable,exchange->auto_delete,args_str.c_str());
                //C++风格
                std::stringstream ss;
                ss<<"insert into exchange_table values(";
                ss<<"'"<<exchange->name<<"',";
                ss<<exchange->type<<",";
                ss<<exchange->durable<<",";
                ss<<exchange->auto_delete<<",";
                ss<<"'"<<exchange->getArgs()<<"');";
                bool ret =_sql_helper.exec(ss.str(),nullptr,nullptr);
                if(ret==false)
                {
                    DLOG("新增交换机数据表失败!!!");
                    return false;
                }
                return true;
            }
            void remove(const std::string &name)
            {
                std::stringstream ss;
                ss<<"delete from exchange_table where name=";
                ss<<"'"<<name<<"';";
                bool ret =_sql_helper.exec(ss.str(),nullptr,nullptr);
                if(ret==false)
                {
                    DLOG("移除交换机数据表失败!!!");
                    return;
                }
            }
            // Exchange::ptr getOne(const std::string &name);
            ExchangeMap Recovery()//为什么要用到unordered_map,这是因为我们内存管理在查询的时候用的是unordered_map ,所以如果你用vector返回那么就不好对应了!!!
            {
                //读取到表中的数据,然后取出来
                ExchangeMap result;
                std::string sql="select name,type,durable,auto_delete,args from exchange_table;";
                bool ret=_sql_helper.exec(sql,selectcallback,&result);
                return result;
            }
        private:
            static int selectcallback(void* arg,int numcol,char** row,char**fields)
            {
                ExchangeMap *result=(ExchangeMap*) arg;
                auto exp=std::make_shared<Exchange>();
                exp->name=row[0];
                exp->type=(cwzmq::ExchangeType)std::stoi(row[1]);
                exp->durable=(bool)std::stoi(row[2]);
                exp->auto_delete=(bool)std::stoi(row[3]);
                if(row[4]) exp->setArgs(row[4]);
                result->insert(std::make_pair(exp->name,exp));
                return 0;
            }
        private:
            cwzmq::SqilteHelper _sql_helper;
    };



    //定义我们交换机数据内存管理类
    class Exchangemanager{
        public:
            //对外
            using ptr=std::shared_ptr<Exchangemanager>;
            Exchangemanager(const std::string &dbfile):_mapper(dbfile)
            {
                _exchanges=_mapper.Recovery();//直接在持久化快进行恢复,那么在初始化Exchangemananger就不用初始化操作
            }
            bool declaredExchange(const std::string &name,ExchangeType type,bool durable,bool auto_delete, const google::protobuf::Map<std::string,std::string> &args)
            {
                //声明交换机
                std::unique_lock<std::mutex> lock(_mutex);
                //如果交换机已经存在,那么久不需要添加直接返回,为什么不调用函数接口exists因为exists中函数也有锁避免同一把锁加俩次那么这里就需要单独取实现判断
                auto it=_exchanges.find(name);
                if(it!=_exchanges.end())
                {
                    DLOG("在_exchanges中已经有名为%s的交换机",name.c_str());
                    return true;
                }
                auto exp=std::make_shared<Exchange>(name,type,durable,auto_delete,args);
                //持久化存储
                if(exp->durable==true)
                {
                    bool ret=_mapper.insert(exp);
                    if(ret==false)
                    {
                        return false;
                    }
                }
                //会被多线程调用+锁
                _exchanges.insert(std::make_pair(name,exp));
                return true;
            }
            //删除
            void deleteExchange(const std::string &name){
                std::unique_lock<std::mutex> lock(_mutex);
                auto it=_exchanges.find(name);
                if(it==_exchanges.end())
                {
                    return ;
                }
                if(it->second->durable==true)  _mapper.remove(name);
                _exchanges.erase(name);
            }
            //获取交换机的数据
            Exchange::ptr selectExchange(const std::string &name)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it=_exchanges.find(name);
                if(it==_exchanges.end())
                {
                    //不存在返回一个空的指针
                    return Exchange::ptr();
                }
                //成功返回一个交换机结构体==数据
                return it->second;
            }
            //判断存在
            bool exists(const std::string &name)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it=_exchanges.find(name);
                if(it==_exchanges.end())
                {
                    return false;
                }
                return true;
            }
            //清理所有交换机
            void clear()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _mapper.removetable();
                _exchanges.clear();
            }
            size_t size()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                return _exchanges.size();
            }
        private:
            //当然这个存在线程安全问题,有可能我们的Exchangemanager对象有可能在多线程进行被使用
            std::mutex _mutex;
            ExchangeMapper _mapper;//持久化的管理
            //内存数据化的管理 ,这里就对应了持久化管理getAll的地方,管理所有交换机映射信息
            ExchangeMap _exchanges;  
    };
}

(7)队列数据管理

队列数据管理

  • 定义队列描述数据类
  1. 队列名称
  2. 是否持久化标志
  • 定义队列数据持久化类(数据持久化的 sqlite3 数据库中)
  1. 创建/删除队列数据表
  2. 新增队列数据
  3. 移除队列数据
  4. 查询所有队列数据
  • 定义队列数据管理类
  1. 创建队列,并添加管理(存在则 OK,不存在则创建)
  2. 删除队列
  3. 获取指定队列
  4. 获取所有队列
  5. 判断指定队列是否存在
  6. 获取队列数量
  7. 销毁所有队列数据
cpp 复制代码
namespace cwzmq{
    class MsgQueue{
        public:
            using ptr=std::shared_ptr<MsgQueue>;
            std::string name;
            bool durable;
            bool exclusive;
            bool auto_delete;
            google::protobuf::Map<std::string, std::string> args;
        MsgQueue(const std::string &name, bool durable, bool exclusive, bool auto_delete, const google::protobuf::Map<std::string, std::string> &args)
            : name(name), durable(durable), exclusive(exclusive), auto_delete(auto_delete), args(args) {}
        MsgQueue()
        {}
        void setArgs(const std::string &str_args)//内部会进行解析str_args字符串,然后将内容存储到unordered_map args 成员中
        {
                std::cout << "setArgs: " << str_args << std::endl;
                //存储格式:key=val&key=val..... 根据字符串进行分割
                std::vector<std::string> sub_str;
                cwzmq::StrHelper::split(str_args,"&",sub_str);
                for(auto &str:sub_str)
                {
                    size_t pos=str.find("=");
                    std::string key=str.substr(0,pos);
                    std::string val=str.substr(pos+1);
                    args[key]=val;
                }
        }
        //序列化,针对这个args内容序列化之后----key=val&key=val.....---,按照指定格式字符串进行返回
        std::string getArgs()
        {
                std::string result;
                for(auto start=args.begin();start!=args.end();++start)
                {
                    std::cout << "start->first: " << start->first << "   " <<  "start->second:" << start->second << std::endl;
                    result += start->first+"="+start->second+"&";
                }
                return result;
        }
        ~MsgQueue()
        {}
    };
    using QueueMap=std::unordered_map<std::string,cwzmq::MsgQueue::ptr>;
    class MsgQueueMapper{
        public:
            using ptr=std::shared_ptr<MsgQueueMapper>;
            MsgQueueMapper(const std::string &dbfile):_sql_helper(dbfile)
            {
                //创建数据库文件所在目录
                std::string parentdir=cwzmq::FileHelper::parentDirectory(dbfile);
                cwzmq::FileHelper::createDirectory(parentdir);
                //创建数据库文件
                _sql_helper.open();
                createtable();
            }
            void createtable()
            {
                // std::string sql1="create table if not exists queue_table(name varchar(32) primary key,durable int,exclusive int,auto_delete int,args varchar(256))";
                std::stringstream sql;
                sql<<"create table if not exists queue_table(";
                sql<<"name varchar(32) primary key,";
                sql<<"durable int,";
                sql<<"exclusive int,";
                sql<<"auto_delete int,";
                sql<<"args varchar(256))";
                // bool ret=_sql_helper.exec(sql.str(),nullptr,nullptr);
                // if(ret==0)
                // {
                //     ELOG("创建队列数据表失败");
                //     abort();
                // }
                assert(_sql_helper.exec(sql.str(),nullptr,nullptr));
            }
            void removetable()
            {
                std::stringstream ss;
                ss<<"drop table if exists queue_table;";
                bool ret=_sql_helper.exec(ss.str(),nullptr,nullptr);
                if(ret==0)
                {
                    ELOG("删除队列数据表失败");
                    abort();
                }
            }
            bool insert(MsgQueue::ptr &queue)
            {
                std::stringstream ss;
                ss<<"insert into queue_table values(";
                ss<<"'"<<queue->name<<"'";
                ss<<","<<queue->durable<<",";
                ss<<queue->exclusive<<",";
                ss<<queue->auto_delete<<",";
                ss<<"'"<<queue->getArgs()<<"')";
                bool ret=_sql_helper.exec(ss.str(),nullptr,nullptr);
                if(ret==0)
                {
                    ELOG("新增队列数据表失败");
                    abort();
                    return false;
                }
                return true;
            }
            bool remove(const std::string &name)
            {
                std::stringstream ss;
                ss<<"delete from queue_table where name=";
                ss<<"'"<<name<<"';";
                bool ret=_sql_helper.exec(ss.str(),nullptr,nullptr);
                if(ret==0)
                {
                    ELOG("移除%s队列数据表失败",name.c_str());
                    abort();
                }
                return true;
            }
            QueueMap Recovery()
            {
                QueueMap result;
                std::string sql="select *from queue_table;";
                bool ret= _sql_helper.exec(sql,selectcallback,&result);
                if(ret==0)
                {
                    ELOG("恢复队列数据表失败");
                    abort();
                }
                return result;
            }
        private:
            static int selectcallback(void* arg,int numcol,char** row,char**fields)
            {
                QueueMap *result=(QueueMap*) arg;
                auto exp=std::make_shared<MsgQueue>();
                exp->name=row[0];
                exp->durable=(bool)std::stoi(row[1]);
                exp->exclusive=(bool)std::stoi(row[2]);
                exp->auto_delete=(bool)std::stoi(row[3]);
                if(row[4]) exp->setArgs(row[4]);
                result->insert(std::make_pair(exp->name,exp));
                return 0;
            }
        private:
            cwzmq::SqilteHelper _sql_helper;
    };

    class MsgQueueManager{
        public:
            using ptr=std::shared_ptr<MsgQueueManager>;
            MsgQueueManager(const std::string &dbfile):_queue_mapper(std::make_shared<MsgQueueMapper>(dbfile))
            {
                _queues=_queue_mapper->Recovery();
            }
            //声明队列
            bool declaredQueue(const std::string &qname,bool qdurable,bool qexclusive,bool qauto_delete,const google::protobuf::Map<std::string,std::string> &qargs)
            {
                std::unique_lock<std::mutex> lock(_lock);
                auto it = _queues.find(qname);
                if(it!=_queues.end())
                {
                    return true;
                }
                MsgQueue::ptr queue=std::make_shared<MsgQueue>(qname,qdurable,qexclusive,qauto_delete,qargs);
                // queue->name=qname;
                // queue->durable=qdurable;
                // queue->exclusive=qexclusive;
                // queue->auto_delete=qauto_delete;
                // queue->args=qargs;
                if(queue->durable) 
                {
                    bool ret=_queue_mapper->insert(queue);
                    if(ret==false)
                    {
                        ELOG("新增队列%s到数据库失败",qname.c_str());
                        _queues.erase(qname);
                        return false;
                    }
                }
                _queues.insert(std::make_pair(qname,queue));
                return true;
            }
            //查询队列
            MsgQueue::ptr selectQueue(const std::string &name)
            {
                std::unique_lock<std::mutex> lock(_lock);
                auto it =_queues.find(name);
                if(it==_queues.end())
                {
                    ELOG("队列%s不存在",name.c_str());
                    return nullptr;
                }
                return it->second;
            }
            void deleteQueue(const std::string &name)
            {
                std::unique_lock<std::mutex> lock(_lock);
                auto it =_queues.find(name);
                if(it==_queues.end())
                {
                    ELOG("没有找到数据");
                    return ;
                }
                if(it->second->durable)
                {
                    bool ret=_queue_mapper->remove(name);
                    if(ret==false)
                    {
                        ELOG("删除队列%s从数据库失败",name.c_str());
                        return ;
                    }
                }
                _queues.erase(name);
            }
            bool exists(const std::string &name)
            {
                std::unique_lock<std::mutex> lock(_lock);
                return _queues.find(name)!=_queues.end();
            }
            QueueMap allQueues()
            {
                std::unique_lock<std::mutex> lock(_lock);
                return _queues;
            }
            size_t size()
            {
                std::unique_lock<std::mutex> lock(_lock);
                return _queues.size();
            }
            void clear()
            {
                std::unique_lock<std::mutex> lock(_lock);
                _queue_mapper->removetable();
                _queues.clear();
            }
        private:
            std::mutex _lock;
            MsgQueueMapper::ptr _queue_mapper;
            QueueMap _queues;
    };
}

(8)绑定模块

  • 定义绑定信息类

    1. 交换机名称
    2. 队列名称
    3. binding_key(分发匹配规则-决定了哪些数据能被交换机放入队列)
  • 定义绑定信息数据持久化类(数据持久化的 sqlite3 数据库中)

    1. 创建/删除绑定信息数据表
    2. 新增绑定信息数据
    3. 移除指定绑定信息数据
    4. 移除指定交换机相关绑定信息数据:移除交换机的时候会被调用
    5. 移除指定队列相关绑定信息数据:移除队列的时候会被调用
    6. 查询所有绑定信息数据:用于重启服务器时进行历史数据恢复
  • 定义绑定信息数据管理类

    1. 创建绑定信息,并添加管理(存在则 OK,不存在则创建)

    2. 解除指定的绑定信息

    3. 删除指定队列的所有绑定信息

    4. 删除交换机相关的所有绑定信息

    5. 获取交换机相关的所有绑定信息:交换机收到消息后,需要分发给队列

    6. 判断指定绑定信息是否存在

    7. 获取当前绑定信息数量

    8. 销毁所有绑定信息数据

cpp 复制代码
namespace cwzmq{
    class Binding{
        public:
            using ptr=std::shared_ptr<Binding>;
            std::string exchange_name;
            std::string queue_name;
            std::string binding_key;
            Binding(const std::string &exchange_name,const std::string &queue_name,const std::string &binding_key)
            :exchange_name(exchange_name),queue_name(queue_name),binding_key(binding_key){}

    };
    //队列与绑定信息1:1
    //交换机与绑定队列1:N
    using MsgQueueBindingMap=std::unordered_map<std::string,Binding::ptr>;
    //定义一个交换机名称与多个队列的绑定信息映射,因为一个交换机可以绑定多个队列,
    // 所以这个map中包含了多个MsgQueueBindingMap,也就是所有的绑定信息,并且以交换机为单元来进行区分
    using BindingMap=std::unordered_map<std::string,MsgQueueBindingMap>;
    class BindingMapper{
        public:
            using ptr=std::shared_ptr<BindingMapper>;
            BindingMapper(const std::string &db_file):_sql_helper(db_file)
            {
                std::string path=cwzmq::FileHelper::parentDirectory(db_file);
                cwzmq::FileHelper::createDirectory(path);
                _sql_helper.open();
                createTable();
            }
            void createTable(){
                std::stringstream ss;
                ss<<"create table if not exists binding_table(";
                ss<<"exchange_name varchar(32),";
                ss<<"queue_name varchar(32),";
                ss<<"binding_key varchar(128));";
                assert(_sql_helper.exec(ss.str(),nullptr,nullptr));
            } 

            void removeTable(){
                std::string sql="drop table if exists binding_table;";  
                assert(_sql_helper.exec(sql,nullptr,nullptr));
            }
            bool insert(Binding::ptr &binding)
            {
                std::stringstream ss;
                ss<<"insert into binding_table values(";
                ss<<"'"<<binding->exchange_name<<"',";
                ss<<"'"<<binding->queue_name<<"',";
                ss<<"'"<<binding->binding_key<<"')";    
                bool ret=_sql_helper.exec(ss.str(),nullptr,nullptr);
                if(ret==0)
                {
                    ELOG("插入绑定数据表失败");
                    abort();
                }
                return ret;
            }
            void remove(const std::string &ename,const std::string &qname)
            {
                std::stringstream ss;
                ss<<"delete from binding_table where exchange_name='";
                ss<<ename<<"'";
                ss<<"and queue_name='";
                ss<<qname<<"';";
                assert(_sql_helper.exec(ss.str(),nullptr,nullptr));
            }
            //移除指定交换机的所有绑定
            void removeExchangeBinding(const std::string &ename)
            {
                //delete from binding_table where exchange_name='ename';
                std::stringstream ss;
                ss<<"delete from binding_table where exchange_name='";
                ss<<ename<<"';";
                assert(_sql_helper.exec(ss.str(),nullptr,nullptr));
            }
            //移除指定队列的所有绑定
            void removeQueueBinding(const std::string &qname)
            {
                std::stringstream ss;
                ss<<"delete from binding_table where queue_name='";
                ss<<qname<<"';";
                assert(_sql_helper.exec(ss.str(),nullptr,nullptr));
            }
            BindingMap recovery()
            {
                std::stringstream ss;
                ss<<"select *from binding_table;";
                BindingMap result;
                bool ret=_sql_helper.exec(ss.str(),selectcallback,&result);
                if(ret==0)
                {
                    ELOG("恢复绑定数据表失败");
                    abort();
                }
                return result;
            }
        private:
            static int selectcallback(void *arg, int argc, char **row, char **azColName)
            {

                BindingMap *result=(BindingMap*)arg;
                std::string exchange_name=row[0];
                std::string queue_name=row[1];
                std::string binding_key=row[2];
                Binding::ptr binding = std::make_shared<Binding>(exchange_name,queue_name,binding_key);     
                //为了防止,交换机相关的绑定信息已经存在,因此不能直接创建队列映射,进行添加,这样会覆盖历史数据
                //这里本身如果map不存存在,那么result会自动创建[exchange_name]
                MsgQueueBindingMap &map=(*result)[binding->exchange_name];
                //用引用&---->如果不存在则会自动创建
                map.insert(std::make_pair(binding->queue_name,binding));
                return 0;
            }
        private:
            cwzmq::SqilteHelper _sql_helper;
    };

    class BindingManager{
        public:
            using ptr=std::shared_ptr<BindingManager>;
            BindingManager(const std::string &db_file)
            :_binding_mapper(db_file){
                _bindings=_binding_mapper.recovery();
            }
            //绑定交换机和队列
            bool bind(const std::string &ename,const std::string &qname,const std::string &binding_key,bool durable)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it =_bindings.find(ename);
                if(it!=_bindings.end()&&it->second.find(qname)!=it->second.end())
                {
                    return true;//交换机和队列已经绑定了
                }
                cwzmq::Binding::ptr binding=std::make_shared<Binding>(ename,qname,binding_key);
                if(durable)
                {
                    bool ret=_binding_mapper.insert(binding);
                    if(ret==0)
                    {
                        return false;
                    }
                }
                //没有则创建一个新的,有的话则直接覆盖,它会返回自己创建的空pbmap引用然后当qbmap进行insert的时候
                //name随之的_bindings[ename]的这个qpmab也会同步更新  这样实现一举俩的

                auto &qbmap=_bindings[ename];
                qbmap.insert(std::make_pair(qname,binding));
                std::cout << "数据插入成功: " << ename << " " << qname << std::endl;
                return true;
            }

            bool unbind(const std::string &ename,const std::string &qname)
            {       
                std::unique_lock<std::mutex> lock(_mutex);
                auto it=_bindings.find(ename);
                if(it==_bindings.end())
                {
                    return false;//没有交换机的绑定信息
                }
                auto it1=it->second.find(qname);
                if(it1==it->second.end())
                {
                    return false;//没有交换机没有队列相关的的绑定信息
                }
                //从_dingdings中删除了队列的绑定信息
                _binding_mapper.remove(ename,qname);
                _bindings[ename].erase(qname);
                return true;
            }
            void removeexchange(const std::string &ename)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _bindings.erase(ename);
                _binding_mapper.removeExchangeBinding(ename);
            }
            void removequeue(const std::string &qname)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                for(auto &it:_bindings)
                {
                    //遍历每一个交换机的绑定信息,从而移除指定队列的相关信息
                    it.second.erase(qname);
                    _binding_mapper.removeQueueBinding(qname);
                }
            }
            MsgQueueBindingMap getExchangBinding(const std::string &ename)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto eit=_bindings.find(ename);
                if(eit==_bindings.end())
                {
                    return MsgQueueBindingMap();//没有交换机的绑定信息
                }
                return eit->second;
            }
            cwzmq::Binding::ptr getBinding(const std::string &ename,const std::string &qname)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto eit=_bindings.find(ename);
                if(eit==_bindings.end())
                {
                    return  cwzmq::Binding::ptr();
                }
                auto qit=eit->second.find(qname);
                if(qit==eit->second.end())
                {
                    return cwzmq::Binding::ptr();
                }
                return qit->second;
            }
            bool exists(const std::string &ename,const std::string &qname)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto eit=_bindings.find(ename);
                if(eit==_bindings.end())
                {
                    return false;
                }
                auto qit=eit->second.find(qname);
                if(qit==eit->second.end())
                {
                    return false;
                }
                return true;
            }
            size_t size()
            {
                std::unique_lock<std::mutex>lock(_mutex);
                size_t size=0;
                for(auto &it:_bindings)
                {
                    size+=it.second.size();
                }
                return size;
            }
            void Clear()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _binding_mapper.removeTable();
                _bindings.clear();
            }
        private:
            std::mutex _mutex;
            BindingMapper _binding_mapper;
            BindingMap _bindings;
    };
}

(9)队列消息管理

创建消息类型的 proto 文件,并使用 protobuf 命令生成相对应的代码文件。

  1. 属性:消息 ID, 路由主题,持久化模式标志
  2. 消息内容
  3. 有效标志(持久化需要)
  4. 持久化位置(内存中)
  5. 持久化消息长度(内存中)

消息的持久化管理

  • 管理数据

    1. 队列消息文件存储的路径
    2. 队列消息的存储文件名
    3. 队列消息的临时交换文件名
  • 管理操作

    1. 日志消息存储在文件中(4B 长度+(属性+内容+有效位)序列化消息,连续
      存储即可)
    2. 提供队列消息文件创建/删除功能
    3. 提供队列消息的新增持久化/删除持久化
    4. 提供持久化内容的垃圾回收(其实就是重新加载出所有有效消息返回,并
      重新生成新的消息存储文件)

消息的管理(以队列为单位进行管理)

  • 队列消息管理数据

    1. 队列名称
    2. 待推送消息链表
    3. 持久化消息 hash
    4. . 待确认消息 hash
    5. 有效消息数量
    6. 已经持久化消息总量
    7. 持久化管理句柄
  • 队列管理操作

    1. 新增消息
    2. 获取队首消息(获取的同时将消息加入待确认队列)
    3. 移除指定待确认消息
    4. 获取队列待消费&待确认消息数量
    5. 恢复队列历史消息。
    6. 销毁队列所有消息
    7. 判断队列消息是否为空
  • 消息的总体对外管理

    1. 初始化新建队列的消息管理结构,并创建消息存储文件
    2. 删除队列的消息管理结构,以及消息存储文件
    3. 向指定队列新增消息. 获取指定队列队首消息
    4. 确认指定队列待确认消息(删除)
    5. 判断指定队列消息是否为空

proto文件格式:

cpp 复制代码
syntax="proto3";
package cwzmq;

//交换机模式
enum ExchangeType{
    UNKNOWTYPE=0;
    DIRECT=1;
    FANOUT=2;
    TOPIC=3;
};

//投递模式
enum DeliverMode{
    UNKNOWMODE=0;
    UNDURABLE=1;
    DURABLE=2;
};

//消息属性
//1.id
//2.消息投递模式
//3.匹配规则
message BasicProperties{
    string id=1;
    DeliverMode delivery_mode=2;
    string routing_key=3;
};

//总体消息信息
message Message{
    message Payload{
        //消息的有效载荷
        //1.消息属性
        //2.消息的内容
        //有效标志位
        BasicProperties properties=1;
        string body=2;
        string valid=4;
    };
    //这里要持久化的
    Payload payload =1;
    //消息的位置
    //长度
    //是否有效
    uint32 offset=2;
    uint32 length=3;
};

队列消息管理代码:

cpp 复制代码
namespace cwzmq{
            #define DATAFILE_SUBFIX ".mqd"
            #define TMPFILE_SUBFIX ".mqd.tmp"
            using MessagePtr=std::shared_ptr<cwzmq::Message>;
            class MessageMapper{
                public:
                    MessageMapper(std::string &basedir,const std::string &qname):_qname(qname)
                    {
                        if(basedir.back()!='/')
                        {
                            basedir.push_back('/');
                        }
                        _datafile=basedir+_qname+DATAFILE_SUBFIX;
                        _tmpfile=basedir+_qname+TMPFILE_SUBFIX;
                        if(cwzmq::FileHelper(basedir).exists()==false)
                        {
                            assert(cwzmq::FileHelper::createDirectory(basedir));
                        }
                        createMsgFile();
                    }
                    bool createMsgFile()
                    {
                        // 确保数据文件和临时文件都存在;分别检查并创建,避免提前返回导致临时文件缺失
                        bool ok = true;
                        if(cwzmq::FileHelper(_datafile).exists()==false)
                        {
                            bool ret=cwzmq::FileHelper::createFile(_datafile);
                            if(ret==false)
                            {
                                ELOG("create message file %s failed",_datafile.c_str());
                                ok = false;
                            }
                        }
                        if(cwzmq::FileHelper(_tmpfile).exists()==false)
                        {
                            bool ret=cwzmq::FileHelper::createFile(_tmpfile);
                            if(ret==false)
                            {
                                ELOG("create message file %s failed",_tmpfile.c_str());
                                ok = false;
                            }
                        }
                        return ok;
                    }
                    void removeMsgFile()
                    {
                        //不能删除目录,只能删除文件
                        cwzmq::FileHelper::removeFile(_datafile);
                        cwzmq::FileHelper::removeFile(_tmpfile);
                    }
                    bool insertMsg(MessagePtr &msg)//这里的msg不能是const因为我们新增的时候修改message内容,因为一个数据只有存储在文件中我们才知道这个文件的具体存储位置
                    {
                        return insertMsg(_datafile,msg);
                    }
                    bool removeMsg(MessagePtr &msg)
                    {
                        //1.将msg有效位设置为 '0'
                        //不能直接用payload去设置,因为payload是一个类(有const修饰),我们需要用mutable_payload()去获取指针,然后去设置
                        msg->mutable_payload()->set_valid("0");
                        //2.将msg进行序列化
                        std::string body=msg->payload().SerializeAsString();
                        //3.检查序列化后的长度是否和msg长度一致,就会覆盖原有的数据
                        if(body.size()!=msg->length())
                        {
                            ELOG("message length not match,offset:%d,length:%d,body size:%ld",msg->offset(),msg->length(),body.size());
                            return false;
                        }
                        //4.覆盖原有的数据
                        cwzmq::FileHelper helper(_datafile);
                        bool ret = helper.write(body.c_str(),msg->offset(),body.size());
                        if(ret==false)
                        {
                            ELOG("write message file %s failed",_datafile.c_str());
                            return false;
                        }
                        DLOG("确认消息之后删除持久化消息成功");
                        return true;
                    }
                    //那这个链表遍历更新消息,垃圾回收
                    std::list<MessagePtr> gc()
                    {
                        //加载数据到内存中
                        std::list<MessagePtr> result;
                        bool aaa=load(result);
                        if(aaa==false)
                        {
                            ELOG("load message file %s failed",_datafile.c_str());
                            return result;
                        }
                        // DLOG("垃圾回收,得到有效消息数量:%ld",result.size());
                        //2.将有效数据,进行序列化存储到临时文件
                        bool ret;
                        for(auto &msg:result)
                        {
                            // DLOG("像临时文件写入有效消息:%s",msg->payload().body().c_str());
                            ret=insertMsg(_tmpfile,msg);
                            if(ret==false)
                            {
                                ELOG("insert message file %s failed",_tmpfile.c_str());
                                return result;
                            }
                        }
                        // DLOG("垃圾回收,临时文件大小:%ld",cwzmq::FileHelper(_tmpfile).size());
                        //3.删除源文件
                        ret=cwzmq::FileHelper::removeFile(_datafile);
                        if(ret==false)
                        {
                            // ELOG("remove message file %s failed",_datafile.c_str());
                            return result;
                        }
                        //4.将临时文件重命名为源文件
                        ret=cwzmq::FileHelper(_tmpfile).rename(_datafile);
                        if(ret==false)
                        {
                            // ELOG("rename message file %s success",_tmpfile.c_str());
                            return result;
                        }
                        //5.返回新的有效数据
                        return result;
                    }
                private:
                    bool load(std::list<MessagePtr> &result)
                    {
                        //1.加载文件中所有的有效数据   存储格式:4字节   数据   
                        FileHelper helper(_datafile);
                        size_t offset=0,msg_size;
                        size_t fsize=helper.size();
                        // DLOG("准备加载持久化数据,文件大小:%ld",helper.size());
                        bool ret;
                        while(offset<fsize)
                        {
                            ret=helper.read((char*)&msg_size,offset,sizeof(size_t));
                            if(ret==false)
                            {
                                ELOG("消息长度读取失败");
                                return false;
                            }
                            offset+=sizeof(size_t);
                            std::string msg_body(msg_size,'\0');
                            ret=helper.read(&msg_body[0],offset,msg_size);
                            if(ret==false)
                            {
                                ELOG("read message file %s failed",_datafile.c_str());
                                return false;
                            }
                            offset+=msg_size;
                            //读取完毕进行反序列化
                            MessagePtr msgp=std::make_shared<Message>();
                            msgp->mutable_payload()->ParseFromString(msg_body);//这样消息就全部反序列化到msgp里面了
                            if(msgp->payload().valid()=="0")
                            {
                                // DLOG("加载到无效消息");
                                continue;   
                                
                            }
                            else
                            {
                                // DLOG("加载到有效消息");
                                result.push_back(msgp);
                            }

                        }
                        return true;
                    }
                    bool insertMsg(const std::string &filename,MessagePtr &msg)//这里的msg不能是const因为我们新增的时候修改message内容,因为一个数据只有存储在文件中我们才知道这个文件的具体存储位置
                    {
                        //新增数据在末尾
                        //0.消息序列化获取消息格式化之后的类容
                        //针对有效载荷进行序列化
                        std::string body=msg->payload().SerializeAsString();
                        //1.获取文件属性(名字,大小,有效性,长度,偏移量)
                        cwzmq::FileHelper helper(filename);
                        size_t fsize=helper.size();
                        size_t msg_size=body.size();
                        //写入逻辑 先写入4个字节的数据长度, 2写入指定数据
                        // DLOG("临时文件%s,偏移量:%ld",_tmpfile.c_str(),fsize);
                        bool ret=helper.write((char*)&msg_size,fsize,sizeof(size_t));
                        if(ret==false)
                        {
                            DLOG("写入消息长度失败");

                            return false;
                        }
                        //2.数据写入指定位置
                        ret=helper.write(body.c_str(),fsize+sizeof(size_t),body.size());
                        if(ret==false)
                        {
                            ELOG("write message file %s failed",filename.c_str());
                            return false;
                        }
                        //4.更新msg实际存储信息
                        //更新偏移量和长度
                        msg->set_offset(fsize+sizeof(size_t));
                        msg->set_length(body.size());
                        return true;
                    }
                private:
                    std::string _qname;
                    std::string _datafile;
                    std::string _tmpfile;
            };
            class QueueMessage{
                public:
                    using ptr=std::shared_ptr<QueueMessage>;
                    QueueMessage(std::string &basedir,const std::string &name):
                    qname(name),
                    _mapper(basedir,name),
                    _valid_count(0),
                    _total_count(0)
                    {
                        //回复历史消息
                        // std::unique_lock<std::mutex> lock (_mutex);
                        // _msgs=_mapper.gc();
                        // for(auto &msg:_msgs)
                        // {
                        //     _durable_msgs.insert(std::make_pair(msg->payload().properties().id(),msg));
                        // }
                        // _total_count=_valid_count=_msgs.size();
                        // Recover();
                    }
                    bool Recover()
                    {
                        //lock 保护_msgs和_durable_msgs
                        std::unique_lock<std::mutex> lock (_mutex);
                        _msgs=_mapper.gc();
                        for(auto &msg:_msgs)
                        {
                            _durable_msgs.insert(std::make_pair(msg->payload().properties().id(),msg));
                        }
                        _total_count=_valid_count=_msgs.size();
                        return true;
                    }
                    bool insert(const BasicProperties *bp,const std::string &body,bool queue_is_durable)
                    {
                        //构造消息对象
                        MessagePtr mspg=std::make_shared<Message>();    
                        mspg->mutable_payload()->set_body(body);
                        if(bp!=nullptr)
                        {
                            DeliverMode mode= queue_is_durable?bp->delivery_mode():cwzmq::DeliverMode::UNDURABLE;
                            mspg->mutable_payload()->mutable_properties()->set_id(bp->id());
                            mspg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
                            mspg->mutable_payload()->mutable_properties()->set_routing_key(bp->routing_key());
                        }
                        else
                        {
                            //没有对消息进行任何属性的设计,需要我们自己设计
                            DeliverMode mode= queue_is_durable?DeliverMode::DURABLE:cwzmq::DeliverMode::UNDURABLE;
                            mspg->mutable_payload()->mutable_properties()->set_id(cwzmq::UUID::uuid());
                            mspg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
                            mspg->mutable_payload()->mutable_properties()->set_routing_key("");
                        }
                        //是否需要持久化
                        std::unique_lock<std::mutex> lock (_mutex);
                        if(mspg->payload().properties().delivery_mode()==cwzmq::DeliverMode::DURABLE){
                            mspg->mutable_payload()->set_valid("1");//在持久化存储中表示数据有效
                            //进行持久化存储
                            bool ret=_mapper.insertMsg(mspg);
                            if(ret==false)
                            {
                                ELOG("插入消息到持久化存储失败");
                                return false;
                            }
                            _valid_count++;
                            _durable_msgs.insert(std::make_pair(mspg->payload().properties().id(),mspg));
                        }
                        //内存管理
                        _msgs.push_back(mspg);
                        _total_count++;
                        return true;
                    }
                    bool remove(const std::string &msg_id)//删除之后判断是否要进行垃圾回收
                    {
                        std::unique_lock<std::mutex> lock (_mutex);
                        // 查找消息从待确认中查找
                        auto it=_waitack_msgs.find(msg_id);
                        if(it==_waitack_msgs.end())
                        {
                            DLOG("没有找到要删除的信息");
                            return true;
                        }
                        //更根据消息的持久化模式,决定是否删除持久化
                        if(it->second->payload().properties().delivery_mode()==cwzmq::DeliverMode::DURABLE)
                        {
                            //删除持久化信息
                            bool ret= _mapper.removeMsg(it->second);
                            if(ret==false)
                            {
                                return false;
                            }
                            //删除内存中信息
                            _valid_count--;
                            _durable_msgs.erase(msg_id);
                        }
                        //待确认消息删除
                        _waitack_msgs.erase(msg_id);
                        gc();//垃圾回收
                        return true;
                    }
                    MessagePtr front()
                    {
                        std::unique_lock<std::mutex> lock (_mutex);
                        //获取一条消息从list中
                        if(_msgs.size()==0)
                        {
                            return MessagePtr();
                        }
                        MessagePtr mspg=_msgs.front();
                        _msgs.pop_front();
                        //把获取的消息添加到待确认表中,等待收到消息后进行删除
                        _waitack_msgs.insert(std::make_pair(mspg->payload().properties().id(),mspg));
                        return mspg;
                    }
                    size_t  getable_count()
                    {
                        std::unique_lock<std::mutex> lock(_mutex);
                        return _msgs.size();
                    }
                    size_t  total_count()
                    {
                        std::unique_lock<std::mutex> lock(_mutex);
                        return _total_count;
                    }
                    size_t  waitack_count()
                    {
                        std::unique_lock<std::mutex> lock(_mutex);
                        return _waitack_msgs.size();
                    }
                    size_t durable_count()
                    {
                        std::unique_lock<std::mutex> lock(_mutex);
                        return _durable_msgs.size();
                    }
                    void clear()
                    {
                        std::unique_lock<std::mutex> lock(_mutex);
                        _mapper.removeMsgFile();
                        _msgs.clear();
                        _durable_msgs.clear();
                        _waitack_msgs.clear();
                        _valid_count=0;
                        _total_count=0;
                    }
                private:
                    bool GCCheck(){
                        //持久化的消息总量大于2000,且有效比例低于50%
                        if(_total_count>2000&&_valid_count*10/_total_count<5)
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                    void gc()
                    {
                        //进行垃圾回收,获取垃圾会收后获取链表
                        if(GCCheck()==false) return;
                        std::list<MessagePtr> msgs=_mapper.gc();
                        for(auto &msg:msgs)
                        {
                            //更新每一条消息实际存储
                            auto it=_durable_msgs.find(msg->payload().properties().id());
                            if(it==_durable_msgs.end())
                            {
                                DLOG("垃圾回收后有一条持久化消息没有管理!");
                                _msgs.push_back(msg);//做法:重新添加到推送链表的末尾
                                _durable_msgs.insert(std::make_pair(msg->payload().properties().id(),msg));//然后给它添加到有效信息中
                                continue;
                            }
                            it->second->set_offset(msg->offset());//更新有效信息的offset
                            it->second->set_length(msg->length());//更新有效信息的length
                        }
                        //更新当前的有效消息数量&总的持久化数量
                        _valid_count=msgs.size();
                        _total_count=msgs.size();
                    }
                private:
                    std::mutex _mutex;
                    std::string qname;
                    size_t _valid_count;
                    size_t _total_count;
                    MessageMapper _mapper;
                    std::list<MessagePtr> _msgs;
                    std::unordered_map<std::string,MessagePtr> _durable_msgs;//带持久化哈希
                    std::unordered_map<std::string,MessagePtr> _waitack_msgs;//待确认消息
            };
            class MessageManager{
                public:
                    using ptr=std::shared_ptr<MessageManager>;
                    MessageManager(const std::string &basedir):_basedir(basedir){}
                    void clear()
                    {
                        std::unique_lock<std::mutex> lock(_mutex);
                        for(auto qmsg:_queues)
                        {
                            qmsg.second->clear();
                        }
                    }
                    void InitQueueMessage(const std::string &qname)
                    {
                        QueueMessage::ptr msg;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it!=_queues.end())
                            {
                                DLOG("队列 %s 已经初始化",qname.c_str());
                                return;
                            }
                            msg=std::make_shared<QueueMessage>(_basedir,qname);//不能在构造的时候初始化,因为你构造的时候需要很多时间,
                            //你需要等待所有的消息都被加载到内存中,才能进行后续的操作,所以这里会导致其他队列消息的初始化很慢很慢,在这里我们采取构造初始化分开操作,保证原子性,和多个队列的初始化新增
                            //在这里才采取数据恢复,由多线程操控不用担心时间满
                            _queues.insert(std::make_pair(qname,msg));
                        }
                        msg->Recover();
                    }
                    void destroyQueueMessage(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("队列 %s 没有初始化",qname.c_str());
                                return;
                            }
                            qmp=it->second;
                            _queues.erase(it);
                        }
                        qmp->clear();
                    }
                    bool insert(const std::string &qname,BasicProperties *bp,const std::string body,bool queue_is_durable)
                    {

                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("队列 %s 没有初始化",qname.c_str());
                                return false;
                            }
                            qmp=it->second;
                        }
                        bool ret=qmp->insert(bp,body,queue_is_durable);

                        return ret;

                    }

                    MessagePtr front(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("获取队列 %s 消息失败",qname.c_str());
                                return nullptr;
                            }
                            qmp=it->second;
                        }
                        return qmp->front();
                    }
                    void ack(const std::string &qname,const std::string &msg_id)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("确认队列消息失败 %s----%s",qname.c_str(),msg_id.c_str());
                                return;
                            }
                            qmp=it->second;
                        }
                        qmp->remove(msg_id); 
                        return ;
                    }
                    size_t getable_count(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("获取队列 %s 可获取带推送消息数失败",qname.c_str());
                                return 0;
                            }
                            qmp=it->second;
                        }
                        return qmp->getable_count();
                    }
                    size_t total_count(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("获取队列 %s 总消息数失败",qname.c_str());
                                return 0;
                            }
                            qmp=it->second;
                        }
                        return qmp->total_count();
                    }
                    size_t waitack_count(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("获取队列 %s 等待确认消息失败",qname.c_str());
                                return 0;
                            }
                            qmp=it->second;
                        }
                        return qmp->waitack_count();
                    }
                    size_t durable_count(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("获取队列 %s 持久化消息失败",qname.c_str());
                                return 0;
                            }
                            qmp=it->second;
                        }
                        return qmp->durable_count();
                    }
                    void clear(const std::string &qname)
                    {
                        QueueMessage::ptr qmp;
                        {
                            std::unique_lock<std::mutex> lock(_mutex);
                            auto it =_queues.find(qname);
                            if(it==_queues.end())
                            {
                                DLOG("清除队列 %s 失败",qname.c_str());
                                return;
                            }
                            qmp=it->second;
                        }
                        qmp->clear();
                        return ;
                    }

                private:
                    std::mutex _mutex;
                    std::string _basedir;//所有消息文件存放的目录
                    std::unordered_map<std::string,QueueMessage::ptr> _queues;
            };

}

(10)虚拟机管理

虚拟机模块是对上述三个数据管理模块的整合,并基于数据之间的关联关系进行联合
操作。

  • 定义虚拟机类包含以下成员:
    1. 交换机数据管理模块句柄
    2. 队列数据管理模块句柄
    3. 绑定数据管理模块句柄
    4. 消息数据管理模块句柄
  • 虚拟机包含操作:
    1. 提供声明交换机的功能(存在则 OK,不存在则创建)
    2. 提供删除交换机的功能(删除交换机的同时删除关联绑定信息)
    3. 提供声明队列的功能(存在则 OK,不存在则创建,创建的同时创建队列关联
      消息管理对象)
    4. 提供删除队列的功能(删除队列的同时删除关联绑定信息,删除关联消息管
      理对象及队列所有消息)
    5. 提供交换机-队列绑定的功能
    6. 提供交换机-队列解绑的功能
    7. 提供获取交换机相关的所有绑定信息功能
    8. 提供新增消息的功能
    9. 提供获取指定队列队首消息的功能
    10. 提供消息确认删除的功能
  • 虚拟机管理操作:
    增删查
cpp 复制代码
namespace cwzmq{

    //模块整合
    class VirtualHost{
        public:
        using ptr=std::shared_ptr<VirtualHost>;
        VirtualHost(const std::string &hname,const std::string &basedir,const std::string &dbfile): 
            Virtualname(hname),
            _emp(std::make_shared<Exchangemanager>(dbfile)),
            _qmp(std::make_shared<MsgQueueManager>(dbfile)),
            _bpm(std::make_shared<BindingManager>(dbfile)),
            _mm(std::make_shared<MessageManager>(basedir))
            {
                //获取所有队列信息,通过队列名称恢复历史
                QueueMap qmap = _qmp->allQueues();
                for(auto &it:qmap)
                {
                   _mm->InitQueueMessage(it.first);
                }
            }
            bool declaredExchange(const std::string &name,ExchangeType type,bool durable,bool auto_delete,const google::protobuf::Map<std::string,std::string> &args)
            {
                bool ret=_emp->declaredExchange(name,type,durable,auto_delete,args);
                if(ret==false)
                {
                    DLOG("问题出现在交换机中,交换机创建失败");
                }
                return true;
            }
            void deleteExchange(const std::string &name)
            {
                //删除交换机相关的数据也要删除掉
                _bpm->removeexchange(name);
                _emp->deleteExchange(name);
                return ;
            }
            bool declaredQueue(const std::string &name,bool durable,bool excusive,bool auto_delete, const google::protobuf::Map<std::string,std::string> &args)
            {
                //初始化队列的消息句柄
                //队列创建
                //队列消息初始化
                _mm->InitQueueMessage(name);
                return _qmp->declaredQueue(name,durable,excusive,auto_delete,args);
            }
            void deleteQueue(const std::string &name)
            {
                _mm->destroyQueueMessage(name);
                _bpm->removequeue(name);
                _qmp->deleteQueue(name);
            }
            bool Bind(const std::string &ename,const std::string &qname,const std::string &routing_key)
            {
                Exchange::ptr eptr= _emp->selectExchange(ename);
                DLOG("绑定的ename是:%s ",ename.c_str());
                if(eptr==Exchange::ptr())
                {
                    DLOG("绑定失败交换机不存在:%s",ename.c_str());
                    return false;
                }
                MsgQueue::ptr qptr = _qmp->selectQueue(qname);
                if(!qptr)
                {
                    DLOG("绑定失败队列不存在:%s",qname.c_str());
                    return false;
                }
                _bpm->bind(ename,qname,routing_key,qptr->durable&&eptr->durable);
                return true;
            }
            bool UnBind(const std::string &qname,const std::string &ename)
            {
                return _bpm->unbind(qname,ename);
            }   

            //获取,新增,操作
            bool basicPublishMsg(const std::string &qname,BasicProperties *bp,const std::string &body)
            {
                MsgQueue::ptr mq=_qmp->selectQueue(qname);
                if(!mq)
                {
                    DLOG("发布失败队列不存在:%s",qname.c_str());
                    return false;
                }
                return _mm->insert(qname,bp,body,mq->durable);
            }
            //从队列中获取消息
            MessagePtr basicConsume(const std::string &qname)
            {
                return _mm->front(qname);
            }
            //删除
            void basicAck(const std::string &qname,const std::string &msg_id)
            {
                _mm->ack(qname,msg_id);
            }
            Exchange::ptr selectExchange(const std::string &ename)
            {
                return _emp->selectExchange(ename);
            }
            //获取指定交换机的所有绑定信息
            MsgQueueBindingMap basicGetBinding(const std::string &ename)
            {
                return _bpm->getExchangBinding(ename);
            }
            QueueMap allQueues()
            {
                return _qmp->allQueues();
            }
            
            void clear()
            {
                _emp->clear();
                _qmp->clear();
                _bpm->Clear();
                _mm->clear();
            }

            bool existsexchange(const std::string &name)
            {
                return _emp->exists(name);
            }

            bool existsQueue(const std::string &name)
            {
                return _qmp->exists(name);
            }
            bool existsbinding(const std::string &ename,const std::string &qname)
            {
                return _bpm->exists(ename,qname);
            }
        private:
            const std::string & Virtualname;
            Exchangemanager::ptr _emp;
            MsgQueueManager::ptr _qmp;
            BindingManager::ptr _bpm;
            MessageManager::ptr _mm;

    };
}
#endif

(11)路由模块

  1. 广播交换:直接将消息交给所有绑定的队列,无需匹配
  2. 直接交换:队列绑定信息中的 binding_key 与消息中的 routing_key 一致则匹配成
    功,否则失败。
  3. 主题交换:只有匹配队列主题的消息才会被放入队列中
    其中广播交换和直接交换,都非常简单,唯一较为难以理解的是主题交换。
  • 匹配算法:
    定义一个二维数组来标记每次匹配的结果,通过最终数组末尾位置的结果来查看是否
    整体匹配成功。使用 routing_key 中的每个单词,与 binding_key 中的单词进行逐个匹配,根据匹配结
    果来标记数组内容,最终以数组中的末尾标记来确定是否匹配成功。

1:整体设计思路
核心思想

把 binding_key 和 routing_key 按 . 分割成单词数组

建立二维 DP 表:dp[i][j] 表示:前 i 个 binding 单词 与 前 j 个 routing 单词 是否匹配

根据 *、#、普通单词三种情况进行状态转移

动态规划状态定义

cpp 复制代码
dp[i][j] = 前 i 个 binding 单词
           与
           前 j 个 routing 单词
           是否匹配成功

初始化规则

开辟空间n+1行,m+1列

1.dp[0][0]=true

2.如果binding_key以'#'号开头那么,dp[i][0]=true

状态转移规则

1.普通单词

cpp 复制代码
if (bind[i-1] == rout[j-1])
    dp[i][j] = dp[i-1][j-1];

2.遇到*

cpp 复制代码
if (bind[i-1] == "*")
    dp[i][j] = dp[i-1][j-1];

3.遇到#

cpp 复制代码
if (bind[i-1] == "#")
    dp[i][j] = dp[i-1][j]   // # 不匹配
            || dp[i][j-1]   // # 多匹配一个
            || dp[i-1][j-1];// # 匹配一个

动态规划代码:

cpp 复制代码
static bool route(ExchangeType type,const std::string &routing_key,const std::string &bind_key)
            {
                if(type==ExchangeType::DIRECT)
                {
                    return routing_key==bind_key;
                }
                else if(type==ExchangeType::FANOUT)
                {
                    return true;
                }
                else//主题: news.# nwes.music.pop
                {
                    //动态规划算法,定义一个二维数组进行标记匹配结果,
                    //算法思想,对bindkey和routingkey进行分割,然后定义出对应大小+1的数组
                    //使用routingkey的每一个元素进行比对,然后在二维数组中进行标价
                    //如果匹配成功,那么从左上方继承结果就行了dp[i][j]=dp[i-1][j-1]
                    //2.通配符#匹配成功,不仅可以在左上方继承结果还可以在左边进行继承 # aaa.ddd dp[i][j]=dp[i-1][j-1]|dp[i][j-1]
                    //3.当我们遇到#号分配符,我们不仅可以从左上方和左方还可以从上方继承结果 dp[i][j]=dp[i-1][j-1]|dp[i][j-1]|dp[i-1][j]
                    //4.当我们遇到#.aaa  之后从左上方继承结果为0匹配失败,所以这也不合理,那么我们可以在#号的位置为1,然后后面的字符在继承的时候,如果成功那么就匹配成功 
                    //binding_key以#起始,则这时候需要将我们对应的#号行的第0列置为1,那么才会置为1


                    //实现:
                    //1.分割binding_key和routing_key
                    std::vector<std::string> bind_words,rout_words;
                    int n_bind=cwzmq::StrHelper::split(bind_key,".",bind_words);
                    
                    int n_rout=cwzmq::StrHelper::split(routing_key,".",rout_words);

                    std::vector<std::vector<bool>> dp(n_bind+1,std::vector<bool> (n_rout+1,false));
                    //2.定义数组+1初始化【0】【0】置为1
                    dp[0][0]=true;
                    //3.如果binding_key以#起始,则#号对应的第0列置1
                    for(int i=1;i<=bind_words.size();i++)
                    {
                        if(bind_words[i-1]=="#")
                        {
                            dp[i][0]=true;
                            continue;
                        }
                        break;
                    }
                    //4.routing_key与binding_key进行匹配,标记
                    for(int i=1;i<=bind_words.size();i++)
                    {
                        for(int j=1;j<=rout_words.size();j++)
                        {
                            if(bind_words[i-1]=="*")
                            {
                                dp[i][j]=dp[i-1][j-1];
                            }
                            else if(bind_words[i-1]==rout_words[j-1])
                            {
                                dp[i][j]=dp[i-1][j-1];
                            }
                            else if(bind_words[i-1]=="#")
                            {
                                dp[i][j]=dp[i-1][j]||dp[i][j-1]||dp[i-1][j-1];
                            }
                        }
                    }
                    return dp[n_bind][n_rout];
                }
            }
    };

(12)队列消费者/订阅者管理

客户端这边每当发起一个订阅请求,意味着服务器这边就多了一个订阅者(处理消息的

客户端描述),而这个消费者或者说订阅者它是和队列直接关联的,因为订阅请求中会

描述当前用户想要订阅哪一个队列的消息。

而一个信道关闭的时候,或者队列被删除的时候,那么这个信道或队列关联的消费者

也就没有存在的意义了,因此也需要将相关的消费者信息给删除掉。

  • 定义消费者信息结构
    1. 消费者标识
    2. 订阅的队列名称
    3. 一个消息的处理回调函数(实现的是当发布一条消息到队列,则选择消费者
      进行消费,如何消费?对于服务端来说就是调用这个个回调函数进行处理,其内
      部逻辑就是找到消费者对应的连接,然后将数据发送给消费者对应的客户端)
      i. void(const std::string&, const BasicProperties&, const std::string&)
    4. 是否自动应答标志。(一个消息被消费者消费后,若自动应答,则直接移除待
      确认消息,否则等待客户端确认)
  • 消费者管理--以队列为单元进行管理-队列消费者管理结构
    操作:
    1. 新增消费者:信道提供的服务是订阅队列消息的时候创建
    2. 删除消费者:取消订阅 / 信道关闭 / 连接关闭 的时候删除
    3. 获取消费者:从队列所有的消费者中按序取出一个消费者进行消息的推送
    4. 判断队列消费者是否为空
    5. 判断指定消费者是否存在
    6. 清理队列所有消费者
      元素
    7. 消费者管理结构:vector
    8. 轮转序号:一个队列可能会有多个消费者,但是一条消息只需要被一个消
      费者消费即可,因此采用 RR 轮转
    9. 互斥锁:保证线程安全
    10. 队列名称
  • 对消费者进行统一管理结构
    1. 初始化/删除队列的消费者信息结构(创建/删除队列的时候初始化)
    2. 向指定队列新增消费者(客户端订阅指定队列消息的时候):新增完成的时候
      返回消费者对象
    3. 从指定队列移除消费者(客户端取消订阅的时候)
    4. 移除指定队列的所有消费者(队列被删除时销毁):删除消费者的队列管理单
      元对象
    5. 从指定队列获取一个消费者(轮询获取-消费者轮换消费起到负载均衡的作用)
    6. 判断队列中消费者是否为空
    7. 判断队列中指定消费者是否存在
    8. 清理所有消费者
cpp 复制代码
namespace cwzmq{
    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 cb;//回调函数

        Consumer(const std::string &tag,const std::string &qname,bool auto_ack,const  ConsumerCallback &cb):
                tag(tag),qname(qname),auto_ack(auto_ack),cb(cb)
                {
                    DLOG("new Consumer:%p",this);
                }//有参
        Consumer(){}//无参
        ~Consumer() {
            DLOG("del Consumer: %p", this);
        }
    };
    //一队列为单元的管理结构
    class QueueConsumer{
        public:
            using ptr=std::shared_ptr<QueueConsumer>;
            QueueConsumer(const std::string &qname):_qname(qname),_consumer_seq(0){}//有参
            Consumer::ptr create(const std::string &ctag,const std::string &qname,bool auto_ack,const ConsumerCallback &cb)
            {
                //加锁
                std::unique_lock<std::mutex> lock(_mutex);
                //判断新增消费者是否重建
                for(auto it:_consumers)
                {
                    if(it->tag==ctag)
                    {
                        return Consumer::ptr();
                    }
                }
                //没有重复则新增--构造对象
                auto consumer=std::make_shared<Consumer>(ctag,qname,auto_ack,cb);
                //添加管理器后返回对象       
                _consumers.push_back(consumer);
                
                DLOG("添加了一个消费者对象");
                DLOG("%ld",_consumers.size())
                return consumer;

            }
            void remove(const std::string &ctag)//根据tag删除消费者
            {
                std::unique_lock<std::mutex> lock(_mutex);
                for(auto it=_consumers.begin();it!=_consumers.end();++it)
                {
                    if((*it)->tag==ctag)
                    {
                        DLOG("删除消费者");
                        _consumers.erase(it);
                        return;
                    }
                }
                return;
            }
            Consumer::ptr choose()//轮询选择一个消费者
            {
                std::unique_lock<std::mutex> lock(_mutex);
                if(_consumers.size()==0)
                {
                    DLOG("消费者的个数为%ld",_consumers.size());
                    return Consumer::ptr();
                }
                uint64_t idx=_consumer_seq%_consumers.size();
                _consumer_seq++;
                return _consumers[idx];
            }
            ~QueueConsumer(){}//无参
            bool empty()
            {
                return _consumers.size()==0;
            }
            bool exists(const std::string &ctag)//判断是否存在指定tag的消费者
            {
                std::unique_lock<std::mutex> lock(_mutex);
                for(auto it=_consumers.begin();it!=_consumers.end();++it)
                {
                    if((*it)->tag==ctag)
                    {
                        return true;
                    }
                }
                return false;       
            }
            void clear()//返回消费者数量
            {
                std::unique_lock<std::mutex> lock(_mutex);
                DLOG("清楚全部消费者");
                _consumers.clear();
                _consumer_seq=0;
            }
        private:
            std::string _qname;
            std::mutex _mutex;
            uint64_t _consumer_seq;//轮转序号
            std::vector<Consumer::ptr> _consumers;//消费者列表
    };
    class ConsumerManager{
        public:
            using ptr=std::shared_ptr<ConsumerManager>;
            ConsumerManager(){}//无参   
            //初始化队列消费者
            void initQueueConsumer(const std::string &qname)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it=_queue_consumers.find(qname);
                if(it!=_queue_consumers.end())
                {
                    return;
                }
                //不存在则新增
                _queue_consumers[qname]=std::make_shared<QueueConsumer>(qname);
            }
            //销毁队列消费者
            void destory(const std::string &qname)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                DLOG("销毁一个队列消费者");
                _queue_consumers.erase(qname);
            }
            Consumer::ptr create(const std::string &ctag,const std::string &qname,bool auto_ack,const ConsumerCallback &cb)
            {
                QueueConsumer::ptr qc;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it=_queue_consumers.find(qname);
                    if(it==_queue_consumers.end())
                    {
                        DLOG("queue consumer %s not found",qname.c_str());
                        return Consumer::ptr();
                    }
                    qc=it->second;
                }
                return qc->create(ctag,qname,auto_ack,cb);
            }
            //根据tag删除指定队列的消费者
            void remove(const std::string &ctag,const std::string &queue_name)
            {
                QueueConsumer::ptr qc;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    DLOG("删除一个指定tag的队列消费者");
                    auto it=_queue_consumers.find(queue_name);
                    if(it==_queue_consumers.end())
                    {
                        DLOG("queue consumer %s not found",queue_name.c_str());
                        return;
                    }
                    qc=it->second;
                }
                qc->remove(ctag);
            }
            //从队列中获取一个消费者
            Consumer::ptr choose(const std::string &queue_name)
            {
                QueueConsumer::ptr qc;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_consumers.find(queue_name);
                    if(it==_queue_consumers.end())
                    {
                        DLOG("消费队列中,没有名为%s的队列",queue_name.c_str());
                        return Consumer::ptr();
                    }
                    qc=it->second;
                }

                return qc->choose();
            }
            bool empty(const std::string &queue_name)
            {
                QueueConsumer::ptr qc;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_consumers.find(queue_name);
                    if(it==_queue_consumers.end())
                    {
                        DLOG("queue consumer %s not found",queue_name.c_str());
                        return false;
                    }
                    qc=it->second;
                }
                return qc->empty();
            }
            bool exists(const std::string &ctag,const std::string &queue_name)
            {
                QueueConsumer::ptr qc;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_consumers.find(queue_name);
                    if(it==_queue_consumers.end())
                    {
                        DLOG("queue consumer %s not found",queue_name.c_str());
                        return false;
                    }
                    qc=it->second;
                }
                return qc->exists(ctag);
            }
            void clear()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                DLOG("清楚所有队列消费者");
                _queue_consumers.clear();
            }
        private:
            //队列消费者结构的映射表
            std::mutex _mutex;
            std::unordered_map<std::string,QueueConsumer::ptr> _queue_consumers;
    };


}

(13)信道管理模块

在 AMQP 模型中,除了通信连接 Connection 概念外,还有一个 Channel 的概念,

Channel 是针对 Connection 连接的一个更细粒度的通信信道,多个 Channel 可以使用

同一个通信连接 Connection 进行通信,但是同一个 Connection 的 Channel 之间相互

独立。

而信道模块就是再次将上述模块进行整合提供服务的模块

  • 管理信息:
    1. 信道 ID:信道的唯一标识
    2. 信道关联的消费者:用于消费者信道在关闭的时候取消订阅,删除订阅者信
    3. 信道关联的连接:用于向客户端发送数据(响应,推送的消息)
    4. protobuf 协议处理句柄:网络通信前的协议处理
    5. 消费者管理句柄:信道关闭/取消订阅的时候,通过句柄删除订阅者信息
    6. 虚拟机句柄:交换机/队列/绑定/消息数据管理
    7. 工作线程池句柄(一条消息被发布到队列后,需要将消息推送给订阅了对应
      队列的消费者,过程由线程池完成)
  • 管理操作:
    1. 提供声明&删除交换机操作(删除交换机的同时删除交换机关联的绑定信息)
    2. 提供声明&删除队列操作(删除队列的同时,删除队列关联的绑定信息,消息,
      消费者信息)
    3. 提供绑定&解绑队列操作
    4. 提供订阅&取消订阅队列消息操作
    5. 提供发布&确认消息操作
  • 信道管理
    1. 信道的增删查。

定义请求/响应参数

在我们之前写翻译服务器的时候,接收请求也是用到protobuf进行定义TranslateRequestt请求。

在这里同样定义我们需要进行提供服务器的任何请求

这是一个交换机请求

复制代码
len:4 个字节, 表示整个报文的长度

nameLen: 4 个字节, 表示 typeName 数组的长度

typeName:是个字节数组, 占 nameLen 个字节, 表示请求/响应报文的类型名,作用是分发不同消息到对应的远端接口调用中

protobufData:是个字节数组, 占 len - nameLen - 8 个字节, 表示请求/响应参数数据通过 protobuf 序列化之后的二进制

checkSum:4 个字节, 表示整个消息的校验和, 作用是为了校验请求/响应报文的完整性

中间一段就是交换机的定义的属性

按照 len - nameLen - 8 的长度读取出 protobufData 就可以将读到的二进制数据反序列

化成 ExchangeDeclareArguments 对象进行后续处理。后续的请求报文和这里都是类似的。

cpp 复制代码
syntax = "proto3";

package cwzmq;

import "mq_msg.proto";

//信道打开与关闭
message openChannelRequest{
    string rid=1;
    string cid=2;
};
message closeChannelRequest{
    string rid=1;
    string cid=2;
};
//交换机的声明与删除
message declareExchangeRequest{
    string rid=1;
    string cid=2;
    string exchange_name=3;
    ExchangeType exchange_type=4;
    bool durable=5;
    bool auto_delete=6;
    map<string,string>args=7;
};
//
//交换机的删除
message deleteExchangeRequest{
    string rid=1;
    string cid=2;
    string exchange_name=3;
};

//队列的声明与删除
message declareQueueRequest{
    string rid=1;
    string cid=2;
    string queue_name=3;
    bool exclusive=4;
    bool durable=5;
    bool auto_delete=6;
    map<string,string> args=7;
};

message deleteQueueRequest{
    string rid=1;
    string cid=2;
    string queue_name=3;
};

//队列的绑定与解绑
message queueBindRequest{
    string rid=1;
    string cid=2;
    string exchange_name=3;
    string queue_name=4;
    string binding_key = 5;
};

message queueUnBindRequest{
    string rid=1;
    string cid=2;
    string exchange_name=3;
    string queue_name=4;
}

//消息的发布
message basicPublishRequest{
    string rid=1;
    string cid=2;
    string exchange_name=3;
    string body=4;
    BasicProperties properties=5;
};

//消息的确认
message basicAckRequest{
    string rid=1;
    string cid=2;
    string queue_name=3;//队列名
    string message_id=4;
};

//队列的订阅
message basicConsumeRequest{
    string rid=1;
    string cid=2;
    string consumer_tag=3;
    string queue_name=4;
    bool auto_ack=5;
};
//订阅取消
message basicCancelRequest{
    string rid=1;
    string cid=2;
    string consumer_tag=3;
    string queue_name=4;
};
//消息的推送
message basicConsumeResponse{
    string cid=1;
    string consumer_tag=2;
    string body=3;
    BasicProperties properties=4;
};

//通用相应

message basicCommonResponse{
    string rid=1;
    string cid=2;
    bool ok=3;
}
cpp 复制代码
namespace cwzmq{
        using openChannelRequestPtr=std::shared_ptr<openChannelRequest>;
        using closeChannelRequestPtr=std::shared_ptr<closeChannelRequest>;
        using declareExchangeRequestPtr=std::shared_ptr<declareExchangeRequest>;
        using deleteExchangeRequestPtr=std::shared_ptr<deleteExchangeRequest>;
        using declareQueueRequestPtr=std::shared_ptr<declareQueueRequest>;
        using deleteQueueRequestPtr=std::shared_ptr<deleteQueueRequest>;
        using queueBindRequestPtr=std::shared_ptr<queueBindRequest>;
        using basicPublishRequestPtr=std::shared_ptr<basicPublishRequest>;
        using queueUnBindRequestPtr=std::shared_ptr<queueUnBindRequest>;
        using basicAckRequestPtr=std::shared_ptr<basicAckRequest>;
        using basicConsumeRequestPtr=std::shared_ptr<basicConsumeRequest>;
        using basicCancelRequestPtr=std::shared_ptr<basicCancelRequest>;
        using ProtobufCodecPtr=std::shared_ptr<ProtobufCodec>;
        using basicConsumeResponsePtr=std::shared_ptr<basicConsumeResponse>;
        class Channel{
            public:
                using ptr=std::shared_ptr<Channel>;
                Channel(const std::string &channel_id,const VirtualHost::ptr &host,const ConsumerManager::ptr &cmp,const ProtobufCodecPtr &codec,
                        const muduo::net::TcpConnectionPtr &conn,const threadpool::ptr &pool):
                        _cid(channel_id),
                        _conn(conn),
                        _codec(codec),
                        _cmp(cmp),
                        _host(host),
                        _pool(pool)
                        {}
                ~Channel(){
                        if(_consumer.get()!=nullptr)
                        {
                                //把有关相连的删除掉就行了
                                _cmp->remove(_consumer->tag,_consumer->qname);
                        }
                }
                //交换机声明与删除
                void declareExchange(const declareExchangeRequestPtr &req)
                {
                        bool ret=_host->declaredExchange(req->exchange_name(),req->exchange_type(),req->durable(),req->auto_delete(),req->args());   
                        if(ret==false)         
                        {
                                DLOG("交换机创建失败");
                                return basicResponse(ret,req->rid(),req->cid());  
                        }            
                        //这里声明一个交换机是在数据库中插入一条数据,然后通过内存管理类来管理
                        //因为无论如何我们都要有回应
                        return basicResponse(ret,req->rid(),req->cid());              
                }
                void deleteExchange(const deleteExchangeRequestPtr &req)
                {
                        _host->deleteExchange(req->exchange_name());            
                        //因为无论如何我们都要有回应
                        return basicResponse(true,req->rid(),req->cid());              
                }
                //队列的声明与删除
                void declareQueue(const declareQueueRequestPtr &req)
                {
                        //队列这一块会有俩个,第一个就是队列的声明信息,第二个是队列相关的队列消费者
                        bool ret=_host->declaredQueue(req->queue_name(),req->durable(),req->exclusive(),req->auto_delete(),req->args());
                        if(ret==false)
                        {
                                return basicResponse(false,req->rid(),req->cid());              
                        }
                        //成功声明消费者管理句柄
                        _cmp->initQueueConsumer(req->queue_name());//初始化队列消费者管理句柄
                        return basicResponse(true,req->rid(),req->cid());              
                }
                void deleteQueue(const deleteQueueRequestPtr &req)
                {
                        _cmp->destory(req->queue_name());//销毁队列消费者管理句柄
                        _host->deleteQueue(req->queue_name());
                        //因为无论如何我们都要有回应
                        return basicResponse(true,req->rid(),req->cid());              
                }
                //队列的绑定与解除绑定
                void bindQueue(const queueBindRequestPtr &req)
                {
                        bool ret=_host->Bind(req->exchange_name(),req->queue_name(),req->binding_key());
                        if(ret!=false)
                        {
                                DLOG("交换机--%s--绑定队列--%s--成功",req->exchange_name().c_str(),req->queue_name().c_str());
                        }
                        //因为无论如何我们都要有回应
                        return basicResponse(ret,req->rid(),req->cid());              
                }
                void unbindQueue(const queueUnBindRequestPtr &req)
                {
                        _host->UnBind(req->queue_name(),req->exchange_name());
                        //因为无论如何我们都要有回应
                        return basicResponse(true,req->rid(),req->cid());              
                }


                //消息的发布
                void publishMessage(const basicPublishRequestPtr &req)
                {                       
                        //1.获取交换机数据
                        Exchange::ptr ex=_host->selectExchange(req->exchange_name());
                        if(ex.get()==nullptr)
                        {
                                return basicResponse(false,req->rid(),req->cid());                      
                        }
                        //2.交换路由,判断消息可以发布到哪一个队列当中
                        MsgQueueBindingMap mqbm=_host->basicGetBinding(req->exchange_name());
                        //因为properties有可能为空
                        std::string routing_key;
                        BasicProperties *properties=nullptr;
                        if(req->has_properties())
                        {
                                //获取
                                routing_key=req->properties().routing_key();
                                properties=req->mutable_properties();           
                        }
                        for(auto &it:mqbm)
                        {
                                if(Route::route(ex->type,routing_key,it.second->binding_key))//这里有可能properties为空
                                {
                                        //3.将消息先加入到队列当中(添加消息的管理) 实际上客户端是把消息发送到队列当中
                                        _host->basicPublishMsg(it.first,properties,req->body()); 
                                        //4.将消息消费,像线程池中添加一个消息消费任务(向指定队列的订阅者取推送消息 线程池完成)
                                        auto task=std::bind(&Channel::consume,this,it.first);
                                        _pool->push(task);
                                        
                                }
                        }
                        return basicResponse(true,req->rid(),req->cid());              
                }
                //消息的确认
                void ackMessage(const basicAckRequestPtr &req)
                {
                        _host->basicAck(req->queue_name(),req->message_id());
                        
                        return basicResponse(true,req->rid(),req->cid());              
                }
                //订阅队列的消息
                void subscribeQueue(const basicConsumeRequestPtr &req)
                {
                        //这里的本质就是告诉服务器那个地方有消息了告诉我一声就行
                        //1.判断队列存在
                        bool ret=_host->existsQueue(req->queue_name());
                        if(ret==false)
                        {
                                DLOG("队列不存在");
                                return basicResponse(false,req->rid(),req->cid());              
                        }
                        //2.如果存在就要订阅,创建消费者然后进行管理
                        auto cb1=std::bind(&Channel::callback,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3);
                        //创建了消费者之后那么他的角色就是一个消费者
                        _consumer = _cmp->create(req->consumer_tag(),req->queue_name(),req->auto_ack(),cb1);
                        if(_consumer==Consumer::ptr())
                        {
                                DLOG("创建订阅消费者失败");
                        }
                        //3.返回订阅成功
                        return basicResponse(true,req->rid(),req->cid());              

                }
                //取消订阅队列的消息
                void unsubscribeQueue(const basicCancelRequestPtr &req)
                {
                        _cmp->remove(req->consumer_tag(),req->queue_name());
                        return basicResponse(true,req->rid(),req->cid());                       
                }
            
            private:
                void callback(const std::string &tag,const BasicProperties *bp,const std::string &body)
                {
                        //针对参数组织出我们的推送消息请求,将消息推送给对应的channel的客户端
                        basicConsumeResponse resp;
                        resp.set_cid(_cid);
                        resp.set_consumer_tag(tag);
                        resp.set_body(body);
                        if(bp)
                        {        
                                resp.mutable_properties()->set_id(bp->id());
                                resp.mutable_properties()->set_delivery_mode(bp->delivery_mode());
                                resp.mutable_properties()->set_routing_key(bp->routing_key()); 
                        }
                        //发送回应
                        _codec->send(_conn,resp);//通信链接,和resp    
                }
                void consume(const std::string &qname)
                {
                        //指定队列消费消息
                        MessagePtr msg=_host->basicConsume(qname);
                        if(msg==nullptr)
                        {
                             DLOG("消费任务时,没有找到有效队列,qname=%s",qname.c_str());
                             return ;
                        }
                        //1.从队列中取出消息
                        Consumer::ptr cp=_cmp->choose(qname);
                        if(cp.get()==nullptr)
                        {
                             DLOG("消费任务时,没有找到有效订阅者,qname=%s",qname.c_str());
                             return ;
                        }
                        //2.从队列订阅者取出一个订阅者
                        cp->cb(cp->tag,msg->mutable_payload()->mutable_properties(),msg->payload().body());
                        //3.调用订阅者消息处理函数实现消息推送
                        if(cp->auto_ack)
                        {
                                _host->basicAck(qname,msg->payload().properties().id());
                        }
                        //4.判断如果订阅者自动确认,不需要等待确认,否则需要收到消息确认后在删除
                }
                void basicResponse(bool ok,const std::string &rid,const std::string &cid)
                {
                        basicCommonResponse resp;
                        resp.set_rid(rid);
                        resp.set_cid(cid);
                        resp.set_ok(ok);
                        //发送回应
                        _codec->send(_conn,resp);//通信链接,和resp    
                }
            private:
                std::string _cid;
                Consumer::ptr _consumer;//订阅了队列之后才会创建!!!
                muduo::net::TcpConnectionPtr _conn;
                //这里的protobuf没必要每一个信道都有一个,我们可以构建一个智能指针,
                // 所有的信道都共享这个指针,当信道关闭时,我们可以自动释放这个指针
                // ProtobufCodec _code;//protobuf协议处理器---针对收到的请求数据进行protobuf协议处理
                ProtobufCodecPtr _codec;
                ConsumerManager::ptr _cmp;
                VirtualHost::ptr _host;
                threadpool::ptr _pool;

        };


class ChannelManager{
        public:
                using ptr=std::shared_ptr<ChannelManager>;
                ChannelManager(){}
                bool openChannel(const std::string &channel_id,const VirtualHost::ptr &host,const ConsumerManager::ptr &cmp,const ProtobufCodecPtr &codec,
                                const muduo::net::TcpConnectionPtr &conn,const threadpool::ptr &pool)
                                {
                                        std::unique_lock<std::mutex> lock(_mutex);
                                        auto it=_channels.find(channel_id);
                                        if(it!=_channels.end())
                                        {
                                                DLOG("信道%s已经存在",channel_id.c_str());
                                                return false;
                                        }
                                        auto channel=std::make_shared<Channel>(channel_id,host,cmp,codec,conn,pool);
                                        _channels.insert(std::make_pair(channel_id,channel));
                                        return true;
                                }
                void closeChannel(const std::string &channel_id)
                {
                        std::unique_lock<std::mutex> lock(_mutex);
                        _channels.erase(channel_id);
                }
                Channel::ptr getChannel(const std::string &channel_id)
                {
                        std::unique_lock<std::mutex> lock(_mutex);
                        auto it=_channels.find(channel_id);
                        if(it==_channels.end())
                        {
                                DLOG("channel %s not found",channel_id.c_str());
                                return Channel::ptr();
                        }
                        return it->second;
                }
        private:
                std::mutex _mutex;//互斥锁
                std::unordered_map<std::string,Channel::ptr> _channels;
        };
}

(14)链接管理模块

向用户提供一个用于实现网络通信的 Connection 对象,从其内部可创建出粒度更轻的

Channel 对象,用于与客户端进行网络通信。

  • 成员信息:
    1. 连接关联的信道管理句柄(实现信道的增删查)
    2. 连接关联的实际用于通信的 muduo::net::Connection 连接
    3. protobuf 协议处理的句柄(ProtobufCodec 对象)
    4. 消费者管理句柄
    5. 虚拟机句柄
    6. 异步工作线程池句柄
  • 连接操作:
    1. 提供创建 Channel 信道的操作
    2. 提供删除 Channel 信道的操作
  • 连接管理:
    连接的增删查
cpp 复制代码
namespace cwzmq{
    class Connection{
            public:
                using ptr=std::shared_ptr<Connection>;
                Connection(const VirtualHost::ptr &host,const ProtobufCodecPtr &codec,const threadpool::ptr &pool,const ConsumerManager::ptr &cmp,const muduo::net::TcpConnectionPtr &conn):
                        _host(host),
                        _codec(codec),
                        _pool(pool),
                        _cmp(cmp),
                        _conn(conn),
                        _channels(std::make_shared<ChannelManager>())
                        {}
                ~Connection(){

                }
                void openChannel(const openChannelRequestPtr& req){
                    //1.判断信道是否重复
                    bool ret=_channels->openChannel(req->cid(),_host,_cmp,_codec,_conn,_pool);
                    if(!ret){
                        DLOG("信道不存在且重复了1:%s",req->cid().c_str());
                        return basicResponse(false,req->rid(),req->cid());
                    }
                    //3.给客户端进行恢复
                    return basicResponse(true,req->rid(),req->cid());
                }
                void closeChannel(const closeChannelRequestPtr& req){
                    _channels->closeChannel(req->cid());
                    return basicResponse(true,req->rid(),req->cid());
                }
                Channel::ptr getChannel(const std::string &cid)
                {
                    return _channels->getChannel(cid);
                }
            private:
                void basicResponse(bool ok,const std::string &rid,const std::string &cid)
                {
                        basicCommonResponse resp;
                        resp.set_rid(rid);
                        resp.set_cid(cid);
                        resp.set_ok(ok);
                        //发送回应
                        _codec->send(_conn,resp);//通信链接,和resp    
                }
                void 
            private:
                VirtualHost::ptr _host;//虚拟主机
                ProtobufCodecPtr _codec;
                threadpool::ptr _pool;
                ConsumerManager::ptr _cmp;
                muduo::net::TcpConnectionPtr _conn;
                ChannelManager::ptr _channels;
                
    };

    class ConnectionManager{
            public:
                using ptr=std::shared_ptr<ConnectionManager>;
                ConnectionManager(){}
                void newConnection(const VirtualHost::ptr &host,const ProtobufCodecPtr &codec,const threadpool::ptr &pool,const ConsumerManager::ptr &cmp,const muduo::net::TcpConnectionPtr &conn)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it=_connections.find(conn);
                    if(it!=_connections.end()){
                        return;
                    }
                    Connection::ptr conn1=std::make_shared<Connection>(host,codec,pool,cmp,conn);
                    _connections.insert(std::make_pair(conn,conn1));
                }
                void delConnection(const muduo::net::TcpConnectionPtr &conn)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    _connections.erase(conn);
                }
                Connection::ptr getConnection(const muduo::net::TcpConnectionPtr &conn)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it=_connections.find(conn);
                    if(it==_connections.end()){
                        return Connection::ptr();
                    }
                    return it->second;
                }
                ~ConnectionManager(){}
            private:
                std::mutex _mutex;
                std::unordered_map<muduo::net::TcpConnectionPtr,Connection::ptr> _connections;
    };
}

(15)服务器实现模块

基于我们上面实现的所有模块,在进行组合成消息队列服务器

我们之前在熟悉muduo库类的时候有进行初步服务器的搭建

而现在只是我们调用回调函数/注册的接口 不一样了。其他大致还是一样的

  • _server :Muduo 库提供的一个通用 TCP 服务器, 我们可以封装这个服务器进行
    TCP 通信
  • _baseloop :主事件循环器, 用于响应 IO 事件和定时器事件,主 loop 主要是为了
    响应监听描述符的 IO 事件
  • _codec : 一个 protobuf 编解码器, 我们在 TCP 服务器上设计了一层应用层协议,
    这个编解码器主要就是负责实现应用层协议的解析和封装, 下边具体讲解
  • _dispatcher :一个消息分发器, 当 Socket 接收到一个报文消息后, 我们需要按
    照消息的类型, 即上面提到的 typeName 进行消息分发, 会不不同类型的消息分发相
    对应的的处理函数中,下边具体讲解
  • _consumer: 服务器中的消费者信息管理句柄。
  • _threadpool: 异步工作线程池,主要用于队列消息的推送工作。
  • _connections: 连接管理句柄,管理当前服务器上的所有已经建立的通信连接。
  • _virtual_host :服务器持有的虚拟主机。 队列、交换机 、绑定、消息等数据都是
    通过虚拟主机管理
cpp 复制代码
namespace cwzmq{
    #define DBFILE "/meta.db"
    class BrokerServer{
        public:
        //这个智能指针是针对方法对象指针
        typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
        // typedef std::shared_ptr<bit::TranslateResponse> TranslateResponsePtr;
        // typedef std::shared_ptr<bit::AddResponse> AddResponsePtr;
        BrokerServer(int port,const std::string basedir):_server(&_loop,muduo::net::InetAddress("0.0.0.0",port),
        "Server",
        muduo::net::TcpServer::Option::kReusePort),
        _dispatcher(std::bind(&BrokerServer::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))),
        _virtual_host(std::make_shared<VirtualHost>("host1",basedir,basedir+DBFILE)),
        _consumer_manager(std::make_shared<ConsumerManager>()),//针对历史消息中所有的队列,别忘了,初始化队列的消费者管理结构
        _connection_manager(std::make_shared<ConnectionManager>()),
        _thread_pool(std::make_shared<threadpool>())
        {
            //获取所有的队列信息
            QueueMap all_queues = _virtual_host->allQueues();
            for(auto &it:all_queues)
            {
                //初始化队列的消费者管理结构
                _consumer_manager->initQueueConsumer(it.first);
            }
            //注册请求处理函数
            //1.打开通道
            _dispatcher.registerMessageCallback<cwzmq::openChannelRequest>(std::bind(&BrokerServer::onOpenChannel,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //2.关闭通道
            _dispatcher.registerMessageCallback<cwzmq::closeChannelRequest>(std::bind(&BrokerServer::onCloseChannel,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //3.声明交换机
            _dispatcher.registerMessageCallback<cwzmq::declareExchangeRequest>(std::bind(&BrokerServer::onDeclareExchange,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //4.删除交换机
            _dispatcher.registerMessageCallback<cwzmq::deleteExchangeRequest>(std::bind(&BrokerServer::onDeleteExchange,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //5.声明队列
            _dispatcher.registerMessageCallback<cwzmq::declareQueueRequest>(std::bind(&BrokerServer::onDeclareQueue,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //6.删除队列
            _dispatcher.registerMessageCallback<cwzmq::deleteQueueRequest>(std::bind(&BrokerServer::onDeleteQueue,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //7.绑定队列
            _dispatcher.registerMessageCallback<cwzmq::queueBindRequest>(std::bind(&BrokerServer::onQueueBind,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //8.解绑队列
            _dispatcher.registerMessageCallback<cwzmq::queueUnBindRequest>(std::bind(&BrokerServer::onQueueUnBind,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //9.发布消息
            _dispatcher.registerMessageCallback<cwzmq::basicPublishRequest>(std::bind(&BrokerServer::onBasicPublish,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //10.确认消息
            _dispatcher.registerMessageCallback<cwzmq::basicAckRequest>(std::bind(&BrokerServer::onBasicAck,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //11.消费消息
            _dispatcher.registerMessageCallback<cwzmq::basicConsumeRequest>(std::bind(&BrokerServer::onBasicConsume,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //12.取消消费
            _dispatcher.registerMessageCallback<cwzmq::basicCancelRequest>(std::bind(&BrokerServer::onBasicCancel,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _server.setConnectionCallback(std::bind(&BrokerServer::onConnection,this,std::placeholders::_1));
            _server.setMessageCallback(std::bind(&ProtobufCodec::onMessage,_codec.get(),std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
        }
        void start(){
            _server.start();
            _loop.loop();
        }
    private:
        //1.打开信道
        void onOpenChannel(const muduo::net::TcpConnectionPtr& conn,const openChannelRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("链接来路不明:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            DLOG("创建信道:成功")
            return mcoon->openChannel(message);
        }
        //2.关闭信道
        void onCloseChannel(const muduo::net::TcpConnectionPtr& conn,const closeChannelRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("链接来路不明:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            return mcoon->closeChannel(message);
        }
        //3.声明交换机
        void onDeclareExchange(const muduo::net::TcpConnectionPtr& conn,const declareExchangeRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("声明交换机的时候信道不存在链接来路不明:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("声明交换机的时候信道不存在:%s",message->cid().c_str());
                return ;
            }
            return channel->declareExchange(message);
        }
        //4.删除交换机
        void onDeleteExchange(const muduo::net::TcpConnectionPtr& conn,const deleteExchangeRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("删除交换机的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("找不到对应的链接");
                return ;
            }
            return channel->deleteExchange(message);
        }
        //5.声明队列
        void onDeclareQueue(const muduo::net::TcpConnectionPtr& conn,const declareQueueRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("声明队列的时候信道不存在链接来路不明:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("声明队列的时候信道不存在:%s",message->cid().c_str());
                return ;
            }
            return channel->declareQueue(message);
        }
        //6.删除队列
        void onDeleteQueue(const muduo::net::TcpConnectionPtr& conn,const deleteQueueRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("删除队列的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("找不到对应的链接");
                return ;
            }
            return channel->deleteQueue(message);
        }
        //7.绑定
        void onQueueBind(const muduo::net::TcpConnectionPtr& conn,const queueBindRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("绑定队列的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("绑定队列的时候信道不存在:%s",message->cid().c_str());
                return ;
            }
            return channel->bindQueue(message);
        }
        //8.解绑
        void onQueueUnBind(const muduo::net::TcpConnectionPtr& conn,const queueUnBindRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("解绑队列的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("解绑队列的时候信道不存在:%s",message->cid().c_str());
                return ;
            }
            return channel->unbindQueue(message);
        }
        //9.消息发送
        void onBasicPublish(const muduo::net::TcpConnectionPtr& conn,const basicPublishRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("发送消息的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("确认消息的信道时候链接找不到");
                return ;
            }
            return channel->publishMessage(message);
        }
        //10.消息确认
        void onBasicAck(const muduo::net::TcpConnectionPtr& conn,const basicAckRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("确认消息的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("确认消息的信道时候链接找不到");
                return ;
            }
            return channel->ackMessage(message);
        }
        //11.队列消息订阅
        void onBasicConsume(const muduo::net::TcpConnectionPtr& conn,const basicConsumeRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("订阅消息的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("订阅消息的信道时候链接找不到");
                return ;
            }
            return channel->subscribeQueue(message);
        }
        //12.取消队列消息订阅
        void onBasicCancel(const muduo::net::TcpConnectionPtr& conn,const basicCancelRequestPtr& message,muduo::Timestamp)
        {
            Connection::ptr mcoon=_connection_manager->getConnection(conn);
            if(mcoon.get()==nullptr)
            {
                DLOG("取消订阅消息的时候链接找不到:%s",conn->peerAddress().toIpPort().c_str());
                conn->shutdown();
                return;
            }
            Channel::ptr channel=mcoon->getChannel(message->cid());
            if(channel.get()==nullptr)
            {
                DLOG("取消订阅消息的信道时候链接找不到");
                return ;
            }
            return channel->unsubscribeQueue(message);
        }
        
        void onConnection(const muduo::net::TcpConnectionPtr& conn)
        {
            if(conn->connected())
            {
                LOG_INFO<<"链接建立成功";
                //根据当前的链接对象实例化
                _connection_manager->newConnection(_virtual_host,
                    _codec,
                    _thread_pool,
                    _consumer_manager,
                    conn);
            }
            else
            {
                //根据当前的链接对象删除连接管理对象
                _connection_manager->delConnection(conn);
                LOG_INFO<<"链接关闭";
            }
        }
        void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn,const MessagePtr& message,muduo::Timestamp)
        {
            LOG_INFO<<"onUnknownMessage: "<<message->GetTypeName();
            conn->shutdown();
        }
    private:
        muduo::net::EventLoop _loop;
        muduo::net::TcpServer _server;//服务器对象
        ProtobufDispatcher _dispatcher;//请求分发器-注册请求处理函数的
        ProtobufCodecPtr _codec;//protobuf协议处理对象--针对收到的请求数据进行protobuf协议处理
        VirtualHost::ptr _virtual_host;
        ConsumerManager::ptr _consumer_manager;
        ConnectionManager::ptr _connection_manager;
        threadpool::ptr _thread_pool;

    };
}
相关推荐
better_liang2 小时前
每日Java面试场景题知识点之-MCP协议在Java开发中的应用实践
java·spring boot·ai·mcp·企业级开发
河阿里2 小时前
SpringBoot :使用 @Configuration 集中管理 Bean
java·spring boot·spring
xiaoshuaishuai82 小时前
C# Codex 脚本编写
java·服务器·数据库·c#
Flittly2 小时前
【SpringSecurity新手村系列】(4)验证码功能实现
java·spring boot·安全·spring
Flittly2 小时前
【SpringSecurity新手村系列】(3)自定义登录页与表单认证
java·笔记·安全·spring·springboot
闻缺陷则喜何志丹2 小时前
【背包 组合】P7552 [COCI 2020/2021 #6] Anagramistica|普及+
c++·算法·背包·洛谷·组合
xiaoye-duck2 小时前
【C++:C++11】C++11新特性深度解析:从类新功能、Lambda表达式到包装器实战
开发语言·c++·c++11
一个行走的民2 小时前
C++ Lambda 表达式语法详解
c++
小小码农Come on2 小时前
C++访问QML控件-----QML访问C++对象属性和方法
java·开发语言·c++