概览
最近看到陈硕老师的muduo的7.6实现protobuf编解码器与消息分发器,觉得消息分发器这里写的确实很妙,简述一下背景,做业务的时候我们常会在tcp上制定一个消息格式,通过这些消息进行通讯,消息除了长度,类型,消息体为了最大压缩会使用pb,然后陈老师制作一个如下的消息格式样例
+-+-+-+-+-+-+-+-+-+-+-
+- len +- 4 bytes : 包的总长度
+- pb type name len +- :pb 类型名字长度
+- pb type \0+- : pb 类型名字,提供反序列化功能
+- pb data +- : pb的序列化数据
+- check sum +- 4 bytes: 校验码
+-+-+-+-+-+-+-+-+-+-+-
(虽然我个人觉得第二项pb类型名字没用,因为pb type以为\0结束已经很好的起到了分割符的作用了),当我们收到这个包的时候,进行解包通过pb type和pb data就可以得到一个完整的pb,但是接收pb的回调函数是muduo中处理的很精妙的一点,
因为函数指针以及std::function是不支持多态的,对于一个底层的消息分发器而言要保存所有消息的回调,是无法使用一个函数指针数组去保存的,muduo使用了一个类CallBack将回调函数放在了里面,因为所有的pb消息都继承于message,在这个function使用dynamic_cast将message做了一次转化转成了具体的子类消息,然后才将消息传递到具体的callback中,如下图, 这里很巧妙的一点是使用模板,将子类消息类型保存下来以便于dynamic_cast做转换,同时所有的CallBack都继承一个基类CallBack,那么只需要保存CallBackT指针在分发器中,利用虚函数多态进行调用即可。
2.正文
如概览所描述,muduo使用了一个类CallBack将回调函数放在了里面,使用模板,将子类消息类型保存下来以便于dynamic_cast做转换,同时所有的CallBack都继承一个基类CallBack,那么只需要保存CallBackT指针在分发器中,利用虚函数多态进行调用;来看看一个简单的例子实现
#include <iostream>
#include <algorithm>
#include <optional>
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include <chrono>
#include <thread>
#include <functional>
#include <thread>
#include <condition_variable>
#include <atomic>
#include <memory>
#include <mutex>
#include <any>
using namespace std;
std::mutex mtx;
std::condition_variable cv;
class MsgBase
{
public:
virtual ~MsgBase() {}
};
class MsgDerive1 : public MsgBase
{
public:
~MsgDerive1() override {}
static std::string GetMsgType()
{
return "msg_derive1";
}
};
class CallBack
{
public:
virtual void OnCallBack(const MsgBase &msg) = 0;
};
template <typename T>
class CallBackT : public CallBack
{
public:
typedef std::function<void(const T &msg_derive)> CallBackFun;
CallBackFun cb_;
CallBackT(const CallBackFun &cb) : cb_(cb) {}
// 1
void OnCallBack(const MsgBase &msg) override
{
const T &msg_specify = dynamic_cast<const T &>(msg); // 转换
cb_(msg_specify); // 调用真正的回调函数
}
};
void OutPutDeriveMsg(const MsgDerive1 &msg_derive)
{
std::cout << msg_derive.GetMsgType() << std::endl;
}
int main()
{
std::map<std::string, std::shared_ptr<CallBack>> call_back_map;
// 注册监听MsgDerive1 消息的函数
std::shared_ptr<CallBackT<MsgDerive1>> call_back_t(new CallBackT<MsgDerive1>(std::bind(OutPutDeriveMsg, std::placeholders::_1)));
call_back_map.insert(std::pair(MsgDerive1::GetMsgType(), call_back_t));
//a MsgDerive1 come
MsgDerive1 msg_derive1;
std::shared_ptr<CallBack> msg_handle_call_back = call_back_map.find(msg_derive1.GetMsgType())->second;
msg_handle_call_back->OnCallBack(msg_derive1);
return 0;
}
在main()
中创建了一个call_back_map
,就把它当作消息分发器来保存回调对象,可以看到value是一个std::shared_ptr<CallBack>
, 然后接下来创建一个MsgDerive1
的CallBack对象并将其放入这个map中,接下来一个MsgDerive1
消息过来了,通过在这个map中找到这个消息类型的callback
对象,然后调用虚函数OnCallBack()
,也就是CallBackT::OnCallBack(), 在这里面我们看到对消息进行dynamic_cast
处理完成后,调用我们一开始放入的std::function,很妙~
3/ref
- 3.1 <linux多线程服务器编程>