目录
3.BaseProtocol & BaseConnection
前文:[实现Rpc] 客户端划分 | 框架设计 | common类的实现
- 通过设计 枚举和宏 提高了项目的可读性
通信抽象类的实现
abs tract.hpp
(1)需要实现的功能:
BaseMessage
:基础消息类,包含消息的基本操作如序列化、反序列化等。BaseBuffer
:缓冲区管理,提供对数据的读写操作。BaseProtocol
:协议处理,负责消息的编码和解码。
BaseConnection
:连接管理,包括发送消息、关闭连接等功能。BaseServer
:服务器基类,提供设置回调函数、启动服务器等功能。BaseClient
:客户端基类,提供连接服务器、发送消息等功能。
以上实现的各个类都是基类,具体实现的功能由其派生类来实现。
(2)具体实现:
- 列举出 虚函数名
1.BaseMessage
#pragma once
#include <memory>
#include <functional>
#include "fields.hpp"
class BaseMessage
{
public:
using ptr = std::shared_ptr<BaseMessage>;
virtual ~BaseMessage() {}
virtual void SetId(const std:: string &rid) { _rid = rid; }
virtual std::string GetId() { return _rid; }
virtual void SetMType(MType type) { _type = type; }
virtual MType GetMType() { return _type; }
virtual std::string serialize() = 0;
virtual bool deserialize(const std::string &msg) = 0;
virtual bool check() = 0;
private:
MType _type;
std::string _rid;
};
- 不用 new,通过智能指针来管理
参考 前文 文档学习批注:
示例场景:
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {
// 如果Base没有虚析构函数,这里可能会导致内存泄漏
};
如果基类析构函数不是虚函数,则只会调用BaseMessage
的析构函数,导致派生类资源泄漏。
总结
using
:简化代码,为std::shared_ptr<BaseMessage>
定义别名。virtual
:确保 继承/多态时 对象的正确析构,是基类设计的核心准则之一。
2.Basebuffer
class BaseBuffer {
public:
using ptr = std::shared_ptr<BaseBuffer>;
virtual size_t readableSize() = 0;//可读取 数据大小
virtual int32_t peekInt32() = 0;//peek 窥视
virtual void retrieveInt32() = 0;//re tri eve 移除
virtual int32_t readInt32() = 0;//peek+retrieve 窥视+移除
virtual std::string retrieveAsString(size_t len) = 0;//读取 指定长度
};
⭕为什么叫
int32
?
int32_t
是 C++ 中的一个固定宽度整数类型,保证是 32bit 位长。使用int32
命名(如peekInt32
,retrieveInt32
,readInt32
)是为了明确表明这些函数处理的是32位整数数据。- 32位整数数据是四字节(4 bytes)。8 比特==1 字节
3.BaseProtocol & BaseConnection
class BaseProtocol {//协议
public:
using ptr = std::shared_ptr<BaseProtocol>;
virtual bool canProcessed(const BaseBuffer::ptr &buf) = 0;
//检查 缓冲区 数据是否足够
virtual bool onMessage(const BaseBuffer::ptr &buf, BaseMessage::ptr &msg) = 0;
//从 缓冲区中 解析出一条数据
virtual std::string serialize(const BaseMessage::ptr &msg) = 0;
//序列化
};
class BaseConnection {
public:
using ptr = std::shared_ptr<BaseConnection>;
virtual void send(const BaseMessage::ptr &msg) = 0;
//通过连接 发送
virtual void shutdown() = 0;
//关闭连接
virtual bool connected() = 0;
//建立连接
};
1.pro to col 协议
4.BaseServer
//类型别名
using ConnectionCallback = std::function<void(const BaseConnection::ptr&)>;
using CloseCallback = std::function<void(const BaseConnection::ptr&)>;
using MessageCallback = std::function<void(const BaseConnection::ptr&, BaseMessage::ptr&)>;
class BaseServer {
public:
using ptr = std::shared_ptr<BaseServer>;
virtual void setConnectionCallback(const ConnectionCallback& cb) {
_cb_connection = cb;
//!!!!!!!!实现和BaseConnection部分的解耦合
}
//新建立连接时的 回调函数
virtual void setCloseCallback(const CloseCallback& cb) {
_cb_close = cb;
}
virtual void setMessageCallback(const MessageCallback& cb) {
_cb_message = cb;
}
//启动服务器
virtual void start() = 0;
protected:
ConnectionCallback _cb_connection;
CloseCallback _cb_close;
MessageCallback _cb_message;
};
⭕前文回顾
1. function
前文:[C++11#47] (四) function包装器 | bind 函数包装器 | 结合使用
std::function
的主要好处在于它能够提供一种 统一的方式来处理不同类型的可调用对象,并且有助于减少由于模板实例化造成的代码膨胀问题。
不过,这也伴随着一定的性能代价,因此在选择是否使用std::function
时需要权衡灵活性与性能需求。
2. why 类似于 _cb_close = cb 设计;
实现了 BaseConnectin 和 BaseServer 函数部分的 解耦合传输
- 状态保存:通过将传入的回调函数(如
cb
)赋值给类成员变量(如_cb_connection
),可以在未来的某个时刻根据需要调用这些回调函数。这意味着当特定事件发生时(例如新连接建立、连接关闭或消息到达),可以执行之前设置好的回调逻辑。 - 动态配置:这种设计允许你在运行时动态地改变对象的行为,而不需要修改对象本身的代码。例如,不同的客户端或服务器实例可以根据其具体需求设置不同的回调处理逻辑
- 简化接口设计:通过提供setter方法(如
setConnectionCallback
),用户可以方便地为对象配置回调逻辑,而不必关心内部如何管理和触发这些回调。
5.BaseClient
class BaseClient {
public:
using ptr = std::shared_ptr<BaseClient>;
virtual void setConnectionCallback(const ConnectionCallback& cb) {
_cb_connection = cb;
}
virtual void setCloseCallback(const CloseCallback& cb) {
_cb_close = cb;
}
virtual void setMessageCallback(const MessageCallback& cb) {
_cb_message = cb;
}//消息到来
virtual void connect() = 0;
virtual void shutdown() = 0;
virtual bool send(const BaseMessage::ptr&) = 0;
virtual BaseConnection::ptr connection() = 0;
virtual bool connected() = 0;
protected:
ConnectionCallback _cb_connection;
CloseCallback _cb_close;
MessageCallback _cb_message;
};
BaseConnection
和BaseClient
都定义了send
方法。尽管BaseConnection
已经有了一个虚函数send
,为什么还需要在BaseClient
中定义一个send
方法?
这个问题的答案涉及到 设计模式、职责分离以及接口的一致性和灵活性。
设计考虑
- 职责分离:
BaseConnection
负责处理底层的连接细节,比如发送和接收消息 (send
,shutdown
,connected
)。 - 而
BaseClient
可能需要提供更高层次的功能,例如管理多个连接、处理重连逻辑等。因此,在BaseClient
中定义send
方法可以允许客户端实现特定的发送逻辑,而不必直接操作BaseConnection
的具体实现。
- 抽象层次:虽然
BaseConnection
提供了基本的消息发送功能,但是BaseClient
可能需要对发送的数据进行预处理或后处理(如日志记录、数据加密等)。 - 通过在
BaseClient
中定义自己的send
方法,可以实现这些额外的功能,同时保持与BaseConnection
的接口一致性。
- 接口统一:为
BaseClient
定义send
方法可以使得客户端使用更加直观和统一。用户不需要关心底层的具体连接是如何实现的,只需要调用BaseClient
提供的方法即可完成所需的操作。这符合面向对象设计中的 “面向接口编程,而不是面向实现编程” 的原则。
- 扩展性:如果将来需要改变消息发送的方式(比如增加异步发送的支持),只需修改
BaseClient
中的send
实现,而无需改动BaseConnection
。
继承 vs 重新定义
虽然 BaseClient
可以通过继承直接使用 BaseConnection
的 send
方法,但这样做会限制其灵活性和控制力。通过在 BaseClient
中重新定义 send
方法,可以更灵活地控制发送行为,包括但不限于:
- 在发送前执行某些检查。
- 对消息内容进行转换或编码。
- 处理发送失败的情况,并尝试重新发送。
🎢flag: 后续会将 设计模式 部分进行专栏整理,敬请期待~
in va lid 无效的