前言:
项目git链接 mq/mqdemo/muduo/protobuf/protobuf_client.cpp · 耀空/项目mq - 码云 - 开源中国
使用技术简介:
开发主语言:C++
序列化框架:Protobuf 二进制序列化
网络通信:自定义应用层协议 + muduo 库(对 tcp 长连接的封装、并且使用 epoll 的事件驱动模式,实现高并发服务器与客户端)
源数据信息数据库: SQLite3
单元测试框架: Gtest
protobuf认识
protobuf,就是序列化与反序列化的工具
语法规则
只需要注意三点:
- 声明语法版本
- 声明命名空间名称
- 定义结构对象的描述
注意
编写proto文件:
描述我们想要定义的结构化对象
描述对象中,有什么样的成员,每个成员具有什么样的属性
比如,在proto文件中描述一个学生结构:
姓名,是一个字符串; 学号:是一个长整型; 年龄:整形
编译proto文件:
针对proto文件中的描述,生成一份我们所需要的对应语言的结构化对象数据操作代码
生成的文件有:
.h 中定义了我们所描述的数据结构对象类; .cc 定义实现了结构化对象数据的访问及操作&序列化&反序列化
使用:
引入生成的头文件,在代码中根据需要使用即可
测试代码见:
Protobuf
https://gitee.com/yaokong123/project-mq/tree/master/mq/mqdemo/protobuf
muduo库
为什么不使用原生 socket
自定义应用层协议 + 原生 socket,复杂度较高,不推荐使用;
而使用自定义应用层协议 + muduo 库(对 tcp 长连接的封装、并且使用 epoll 的事件驱动模式,实现高并发服务器与客户端),就可以简便很多,像消息回调,链接回调,新链接事件监控,新链接IO监控,moduo库还有对 protobuf 协议的处理,等等;
Muduo 库是什么
Muduo 由陈硕大佬开发,是一个基于非阻塞 IO 和事件驱动 的 C++高并发 TCP 网络编 程库。
muduo库 基于主从Reactor模型 的高性能服务器框架,线程模型是 one loop per thread;
Reactor模型
Reactor模型:基于事件触发的模型(基于epoll进行IO事件监控)
- 主从Reactor模型:将IO事件监控进行了进一步的层次划分
- 主Reactor:只对新建连接事件进行监控(保证不受IO阻塞影响,实现高效的新建连接获取)
- 从Reactor:针对新建连接进行IO事件监控(进行IO操作和业务处理)
主从Reactor必然是一个多执行流的并发模式------one thread one loop,也就是一个事件监控,占据一个线程,进行事件监控。
one thread one loop
解读: 时间线: 1. client1连接 → 主Reactor接受 → 分配给子Reactor1 2. client2连接 → 主Reactor接受 → 分配给子Reactor2 3. client3连接 → 主Reactor接受 → 分配给子Reactor3 后续事件处理: - client1的所有Read/Write → 子Reactor1线程处理 - client2的所有Read/Write → 子Reactor2线程处理 - client3的所有Read/Write → 子Reactor3线程处理
- 一个线程只能有一个事件循环(EventLoop), 用于响应计时器和 IO 事件
- 一个文件描述符只能由一个线程进行读写,换句话说就是一个 TCP 连接必须归属 于某个 EventLoop 管理
这就是reactor和one thread one loop的好处
- 主Reactor专注接收连接,子Reactor专注处理连接IO
- 每个TCP连接从生到死都由同一个线程处理,通过架构设计避免线程竞争,而不是通过加锁(无锁且线程隔离)
EventLoop已经在muduo封装成了一个类,可以直接拿来使用;
muduo接口认识
建议看一遍接口后,再进行muduo库的编写测试
各种接口我都放在了
这没有什么好说的,我感觉主要就是看函数接口的**参数是什么,怎么用的,功能是啥,**不能用错了;
因为只是为了写这个项目,所以只简单基础认识,可能要用的接口;
介绍的接口有,
- muduo::net::TcpServer 类
- muduo::net::EventLoop 类
- muduo::net::TcpConnection 类
- muduo::net::TcpClient 类
- muduo::net::Buffer 类
Muduo 库快速上手
具体的基础思路
具体实现,看一遍我的代码就十分了解了:Muduo网络库简单实现的服务器和客户端
https://gitee.com/yaokong123/project-mq/tree/master/mq/mqdemo/muduo/dict
基于 muduo 库函数实现 protobuf 协议的通信
首先第一点就是,先定义具体的业务请求类型, 然后实现服务端提供的服务。
实现最重要的就是muduo 库中内部实现的关于简单的基于 protobuf 的 接口类
muduo 库中内部实现的关于简单的基于 protobuf 的 接口类
提前整体解析:
可以最后再看,也可以先看图提前了解大概
提前整体解析:
protobufDispatcher的onProtobufMessage接口就是给上边ProtobufCodec::messageCallback_设置的回调函数,相当于 ProtobufCodec 中 onMessage 接口会设置给服务器作为消息 回调函数,其内部对于接收到的数据进行基于 protobuf 协议的解析,得到请求后,通 过 ProtobufDispatcher::onProtobufMessage 接口进行请求分发处理,也就是确定当前 请求应该用哪一个注册的业务函数进行处理。
ProtobufCodec 类
ProtobufCodec 类是 muduo 库中对于 protobuf 协议的处理类,其内部实现了 onMessage 回调接口,对接收到的数据进行基于 protobuf 协议的请求处理,然后将解析出的信息,存放到对应请求的 protobuf 请求类对象中,然后最终调用设置进去的消息处理回调函数进行对应请求的处理。
- 也就是,muduo库有protobuf协议处理器,可以针对收到的请求数据进行protobuf协议处理
接口(必要的已加注释)
cpp
/*muduo-master/examples/protobuf/codec.h*/
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
//
// FIXME: merge with RpcCodec
//
class ProtobufCodec : muduo::noncopyable
{
public:
enum ErrorCode
{
khuError = 0,
kInvalidLength,
kCheckSumError,
kInvalidNameLen,
kUnknownMessageType,
kParseError,
};
typedef std::function<void (const muduo::net::TcpConnectionPtr&,
const MessagePtr&,
muduo::Timestamp)>
ProtobufMessageCallback;
//这里的 messageCb 是针对 protobuf 请求进行处理的函数,它声明在dispatcher.h 中的 ProtobufDispatcher 类
explicit ProtobufCodec(const ProtobufMessageCallback& messageCb)
: messageCallback_(messageCb), //这就是设置的请求处理回调函数
errorCallback_(defaultErrorCallback)
{
}
//它的功能就是接收消息,进行解析,得到了 proto 中定义的请求后调用设置的messageCallback 进行处理
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp receiveTime);
//通过 conn 对象发送响应的接口
void send(const muduo::net::TcpConnectionPtr& conn,
const google::protobuf::Message& message)
{
// FIXME: serialize to TcpConnection::outputBuffer()
muduo::net::Buffer buf;
fillEmptyBuffer(&buf, message);
conn->send(&buf);
}
static const muduo::string& errorCodeToString(ErrorCode
errorCode);
static void fillEmptyBuffer(muduo::net::Buffer* buf, const
google::protobuf::Message& message);
static google::protobuf::Message* createMessage(const
std::string& type_name);
static MessagePtr parse(const char* buf, int len, ErrorCode*
errorCode);
private:
static void defaultErrorCallback(const
muduo::net::TcpConnectionPtr&,
muduo::net::Buffer*,
muduo::Timestamp,
ErrorCode);
ProtobufMessageCallback messageCallback_;
ErrorCallback errorCallback_;
const static int MHeaderLen = sizeof(int32_t);
const static int MMinMessageLen = 2*MHeaderLen + 2; // nameLen +
// typeName + checkSum
const static int MMaxMessageLen = 64*1024*1024; // same as
// codec_strcam.h kDefaultTotalBytesLimit
};
ProtobufDispatcher 类
ProtobufDispatcher 类,这是一个 protobuf 请求的分发处理类,在使用的时候,就是在这个类对象中注册哪个请求应该用哪个业务函数进行处理。
它内部的 onProtobufMessage 接口就是给上边 ProtobufCodec::messageCallback_设置的回调函数。
- 也就是,一个请求分发器对象,要向其中注册请求处理对应的函数;
接口:
cpp
class Callback : muduo::noncopyable
{
public:
virtual ~Callback() = default;
virtual void onMessage(const muduo::net::TcpConnectionPtr&,
const MessagePtr& message,
muduo::Timestamp) const = 0;
};
// 这是一个模板类,用于处理特定类型的消息
template <typename T>
class CallbackT : public Callback
{
static_assert(std::is_base_of<google::protobuf::Message, T>::value,
"T must be derived from google::protobuf::Message.");
public:
typedef std::function<void (const muduo::net::TcpConnectionPtr&,
const std::shared_ptr<T>& message,
muduo::Timestamp)>
ProtobufMessageCallback;
explicit CallbackT(const ProtobufMessageCallback& callback)
: callback_(callback)
{
}
void onMessage(const muduo::net::TcpConnectionPtr& conn,
const MessagePtr& message,
muduo::Timestamp receiveTime) const override
{
std::shared_ptr<T> concrete =
muduo::down_pointer_cast<T>(message);
assert(concrete != NULL);
callback_(conn, concrete, receiveTime);
}
private:
ProtobufMessageCallback callback_;
};
// 这是一个消息分发器,需要用户注册不同类型消息的处理函数
class ProtobufDispatcher
{
public:
typedef std::function<void (const muduo::net::TcpConnectionPtr&,
const MessagePtr& message,
muduo::Timestamp)>
ProtobufMessageCallback;
// 设置一个默认的回调函数,以便于找不到相应的请求处理器时调用
explicit ProtobufDispatcher(const ProtobufMessageCallback& defaultCb)
: defaultCallback_(defaultCb)
{
}
// 这个函数从网络收到消息后会被调用,内部会根据消息类型查找对应的处理器
void onProtobufMessage(const muduo::net::TcpConnectionPtr& conn,
const MessagePtr& message,
muduo::Timestamp receiveTime) const
{
CallbackMap::const_iterator it = callbacks_.find(message->GetDescriptor());
if (it != callbacks_.end())
{
it->second->onMessage(conn, message, receiveTime);
}
else
{
defaultCallback_(conn, message, receiveTime);
}
}
// 这个接口非常巧妙,基于 protobuf 的反射机制能够获取到消息类型的描述符
// 由于可以通过描述符区分不同的消息类型,所以能够创建不同类型消息的处理器
template<typename T>
void registerMessageCallback(const typename CallbackT<T>::ProtobufMessageCallback& callback)
{
std::shared_ptr<CallbackT<T>> pf(new CallbackT<T>(callback));
callbacks_[T::descriptor()] = pf;
}
private:
typedef std::map<const google::protobuf::Descriptor*,
std::shared_ptr<Callback>> CallbackMap;
CallbackMap callbacks_;
ProtobufMessageCallback defaultCallback_;
};
了解就行(proto 中的请求和对应业务处理函数的映射)
下面了解就行,主要是说为啥能完成 proto 中的请求和对应业务处理函数的映射
是我个人的见解,不一定正确;个人也没太整明白
而且
能实现请求与函数之间的映射,还有一个非常重要的元素:那就是应用层协议
protobuf 比我们 想象中做的事情更多
protobuf 根据我们的 proto 文件生成的代码中,会生成对应类型的类,比如 TranslateRequest 对应了一个 TranslateRequest 类,每个对应的类中,都包含有一个描述结构的指针
cppstatic const ::google::protobuf::Descriptor* descriptor();
- 这个描述结构非常重要,其内部可以获取到当前对应类类型名称,以及各项成员的名称,因此通过这些名称,加上协议中的 typename 字段,就可以实现完美的对应关系 了。
好处,优点
上面,介绍了muduo 库中陈硕大佬提供的接口,其最为简便之处就在于我们可以把更多的精力放到业务处理函数的实现上,而不是服务器的搭建或者协议的解析处理上了。如此就可以编写更简便的客户端/服务器端通信了
测试代码见:
基于muduo库实现protobuf的通讯
https://gitee.com/yaokong123/project-mq/tree/master/mq/mqdemo/muduo/protobuf






