【链接】GitHub - qicosmos/rest_rpc: modern C++(C++11), simple, easy to use rpc framework
rpc_client 是一个用于实现远程过程调用(Remote Procedure Call, RPC)的客户端类。利用 Asio 库进行网络通信,并支持异步操作、SSL 安全连接(如果启用)、超时控制等功能。这个类允许客户端以不同的方式初始化,并提供了灵活的消息处理机制。
核心功能
- 异步网络通信:使用 Asio 提供的异步 I/O 操作来执行非阻塞网络请求。
- SSL 支持:在定义了
CINATRA_ENABLE_SSL
的情况下,可以支持安全的 SSL/TLS 连接。 - 超时管理:通过
asio::steady_timer
实现超时控制,确保在网络异常时能够及时响应。 - 消息队列与缓冲区:维护待发送消息的队列和接收响应数据的缓冲区,保证数据的有序处理。
- 回调机制:通过设置回调函数来处理来自服务器的响应或错误情况。
- 重连机制:提供重连尝试次数配置,增强连接稳定性。
核心成员函数解析
1. 线程管理函数
void run() {
if (thd_ != nullptr && thd_->joinable()) {
thd_->join();
}
}
-
作用:阻塞等待IO线程完成
-
特点:
-
检查线程指针非空且可join
-
安全地等待后台线程结束
-
通常用于客户端销毁前的清理
-
2. 连接配置函数
void set_connect_timeout(size_t milliseconds) {
connect_timeout_ = milliseconds;
}
-
作用:设置连接超时时间(毫秒)
-
使用场景:在connect前调用配置超时
void set_reconnect_count(int reconnect_count) {
reconnect_cnt_ = reconnect_count;
}
-
作用:设置最大重连次数
-
特殊值:-1表示无限重连
void enable_auto_reconnect(bool enable = true) {
enable_reconnect_ = enable;
reconnect_cnt_ = std::numeric_limits<int>::max();
}
-
作用:启用/禁用自动重连
-
特点:启用时设置极大重连次数,模拟无限重连
void enable_auto_heartbeat(bool enable = true) {
if (enable) {
reset_deadline_timer(5);
} else {
deadline_.cancel();
}
}
-
作用:启用/禁用心跳机制
-
参数:
enable
为true时,每5秒发送心跳
3. 连接管理函数
bool connect(size_t timeout = 3, bool is_ssl = false);
bool connect(const std::string &host, unsigned short port,
bool is_ssl = false, size_t timeout = 3);
作用:同步建立连接
流程:
检查是否已连接
必要时升级SSL
异步发起连接
等待连接结果
参数:
timeout
:等待连接完成的超时(秒)
is_ssl
:是否使用SSL加密返回:连接是否成功
void async_connect(const std::string &host, unsigned short port);
作用:异步建立连接
特点:不阻塞调用线程,通过回调通知结果
bool wait_conn(size_t timeout) {
if (has_connected_) return true;
has_wait_ = true;
std::unique_lock<std::mutex> lock(conn_mtx_);
bool result = conn_cond_.wait_for(lock, std::chrono::seconds(timeout),
[this] { return has_connected_.load(); });
has_wait_ = false;
return has_connected_;
}
-
作用:等待连接完成
-
机制:
-
使用条件变量等待连接状态变化
-
带超时的等待避免永久阻塞
-
原子变量保证线程安全
-
4. 连接维护函数
void update_addr(const std::string &host, unsigned short port) {
host_ = host;
port_ = port;
}
作用:更新服务器地址
注意:不会主动断开现有连接
void close(bool close_ssl = true) {
asio::error_code ec;
if (close_ssl) {
#ifdef CINATRA_ENABLE_SSL
if (ssl_stream_) {
ssl_stream_->shutdown(ec);
ssl_stream_ = nullptr;
}
#endif
}
if (!has_connected_) return;
has_connected_ = false;
socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
socket_.close(ec);
clear_cache();
}
作用:关闭连接
流程:
可选关闭SSL连接
关闭TCP socket
清理内部缓存
参数:
close_ssl
控制是否关闭SSL连接
5. 回调设置函数
void set_error_callback(std::function<void(asio::error_code)> f) {
err_cb_ = std::move(f);
}
-
作用:设置全局错误回调
-
回调参数:ASIO错误码
-
触发场景:网络错误、连接断开等
6. 同步调用函数族
6.1 无返回值同步调用
template <size_t TIMEOUT, typename T = void, typename... Args>
typename std::enable_if<std::is_void<T>::value>::type
call(const std::string &rpc_name, Args &&...args) {
auto future_result = async_call<FUTURE>(rpc_name, std::forward<Args>(args)...);
auto status = future_result.wait_for(std::chrono::milliseconds(TIMEOUT));
if (status == std::future_status::timeout ||
status == std::future_status::deferred) {
throw std::out_of_range("timeout or deferred");
}
future_result.get().as();
}
template <typename T = void, typename... Args>
typename std::enable_if<std::is_void<T>::value>::type
call(const std::string &rpc_name, Args &&...args) {
call<DEFAULT_TIMEOUT, T>(rpc_name, std::forward<Args>(args)...);
}
-
机制:
-
使用SFINAE技术限定只适用于返回void的RPC调用
-
通过async_call转换为future-based异步调用
-
等待结果并检查超时
-
默认超时版本委托给具体超时版本实现
-
-
特点:
-
阻塞式调用,直到收到响应或超时
-
超时或异常时抛出std::out_of_range
-
不关心返回值,只确保调用成功
-
6.2 有返回值同步调用
template <size_t TIMEOUT, typename T, typename... Args>
typename std::enable_if<!std::is_void<T>::value, T>::type
call(const std::string &rpc_name, Args &&...args) {
auto future_result = async_call<FUTURE>(rpc_name, std::forward<Args>(args)...);
auto status = future_result.wait_for(std::chrono::milliseconds(TIMEOUT));
if (status == std::future_status::timeout ||
status == std::future_status::deferred) {
throw std::out_of_range("timeout or deferred");
}
return future_result.get().template as<T>();
}
template <typename T, typename... Args>
typename std::enable_if<!std::is_void<T>::value, T>::type
call(const std::string &rpc_name, Args &&...args) {
return call<DEFAULT_TIMEOUT, T>(rpc_name, std::forward<Args>(args)...);
}
-
机制:
-
同样基于future的等待机制
-
使用模板参数T指定返回值类型
-
通过req_result的as<T>()方法转换结果
-
-
特点:
-
返回指定类型的RPC调用结果
-
自动类型转换保证类型安全
-
默认超时版本简化调用
-
7. 异步调用函数族
7.1 基于Future的异步调用
template <CallModel model, typename... Args>
future_result<req_result> async_call(const std::string &rpc_name, Args &&...args) {
auto p = std::make_shared<std::promise<req_result>>();
std::future<req_result> future = p->get_future();
uint64_t fu_id = 0;
{
std::unique_lock<std::mutex> lock(cb_mtx_);
fu_id_++;
fu_id = fu_id_;
future_map_.emplace(fu_id, std::move(p));
}
rpc_service::msgpack_codec codec;
auto ret = codec.pack_args(std::forward<Args>(args)...);
write(fu_id, request_type::req_res, std::move(ret),
MD5::MD5Hash32(rpc_name.data()));
return future_result<req_result>{fu_id, std::move(future)};
}
-
机制:
-
创建promise/future对存储结果
-
生成唯一请求ID并注册到future_map_
-
使用msgpack序列化参数
-
通过MD5哈希函数名生成32位标识
-
写入发送队列
-
-
特点:
-
返回包含future和请求ID的future_result对象
-
线程安全的请求ID生成和映射管理
-
支持链式异步操作
-
7.2 多语言专用异步调用
long internal_async_call(const std::string &encoded_func_name_and_args) {
auto p = std::make_shared<std::promise<req_result>>();
uint64_t fu_id = 0;
{
std::unique_lock<std::mutex> lock(cb_mtx_);
fu_id_++;
fu_id = fu_id_;
}
msgpack::sbuffer sbuffer;
sbuffer.write(encoded_func_name_and_args.data(),
encoded_func_name_and_args.size());
write(fu_id, request_type::req_res, std::move(sbuffer),
MD5::MD5Hash32(encoded_func_name_and_args.data()));
return fu_id;
}
-
特点:
-
专为其他语言(如Java)集成设计
-
接收预编码的函数名和参数
-
只返回请求ID,不管理future
-
结果通过回调机制返回
-
7.3 基于回调的异步调用
template <size_t TIMEOUT = DEFAULT_TIMEOUT, typename... Args>
void async_call(const std::string &rpc_name,
std::function<void(asio::error_code, string_view)> cb,
Args &&...args) {
if (!has_connected_) {
if (cb)
cb(asio::error::make_error_code(asio::error::not_connected),
"not connected");
return;
}
uint64_t cb_id = 0;
{
std::unique_lock<std::mutex> lock(cb_mtx_);
callback_id_++;
callback_id_ |= (uint64_t(1) << 63);
cb_id = callback_id_;
auto call = std::make_shared<call_t>(ios_, std::move(cb), TIMEOUT);
call->start_timer();
callback_map_.emplace(cb_id, call);
}
rpc_service::msgpack_codec codec;
auto ret = codec.pack_args(std::forward<Args>(args)...);
write(cb_id, request_type::req_res, std::move(ret),
MD5::MD5Hash32(rpc_name.data()));
}
-
机制:
-
检查连接状态,未连接立即回调错误
-
生成带标志位的回调ID(最高位设为1)
-
创建带超时的call_t对象管理回调
-
注册回调到callback_map_
-
序列化参数并发送请求
-
-
特点:
-
完全异步的callback-based接口
-
内置超时处理机制
-
错误优先的回调设计
-
线程安全的回调管理
-
私有成员函数深度解析
1. 客户端生命周期管理
停止客户端
void stop() {
if (thd_ != nullptr) {
work_ = nullptr; // 允许io_context自然退出
if (thd_->joinable()) {
thd_->join(); // 等待IO线程结束
}
thd_ = nullptr; // 释放线程资源
}
}
-
机制:
-
清除work对象使io_context可退出
-
等待IO线程完成
-
释放线程资源
-
-
注意:应在析构函数前调用确保资源有序释放
2. 发布-订阅功能
订阅相关函数
template <typename Func>
void subscribe(std::string key, Func f) {
// 检查重复订阅
sub_map_.emplace(key, std::move(f)); // 存储回调
send_subscribe(key, ""); // 发送订阅请求
key_token_set_.emplace(std::move(key), ""); // 记录订阅关系
}
template <typename Func>
void subscribe(std::string key, std::string token, Func f) {
auto composite_key = key + token; // 构造复合键
sub_map_.emplace(std::move(composite_key), std::move(f));
send_subscribe(key, token);
key_token_set_.emplace(std::move(key), std::move(token));
}
void send_subscribe(const std::string &key, const std::string &token) {
auto ret = codec.pack_args(key, token); // 序列化参数
write(0, request_type::sub_pub, std::move(ret), MD5::MD5Hash32(key.data()));
}
void resend_subscribe() {
// 重连后重新发送所有订阅
for (auto &pair : key_token_set_) {
send_subscribe(pair.first, pair.second);
}
}
-
特点:
-
支持带token和不带token的订阅
-
自动管理订阅关系
-
重连后自动恢复订阅
-
发布相关函数
template <typename T, size_t TIMEOUT = 3>
void publish(std::string key, T &&t) {
auto buf = codec.pack(std::move(t)); // 序列化消息
call<TIMEOUT>("publish", std::move(key), "",
std::string(buf.data(), buf.size()));
}
template <typename T, size_t TIMEOUT = 3>
void publish_by_token(std::string key, std::string token, T &&t) {
auto buf = codec.pack(std::move(t));
call<TIMEOUT>("publish_by_token", std::move(key), std::move(token),
std::string(buf.data(), buf.size()));
}
-
特点:
-
支持带超时的发布操作
-
自动序列化发布内容
-
区分普通发布和带token发布
-
3. 连接管理
void async_connect() {
socket_.async_connect({addr, port_}, [this](const asio::error_code &ec) {
if (!ec) {
if (is_ssl()) {
handshake(); // SSL连接需额外握手
return;
}
has_connected_ = true;
do_read(); // 开始接收数据
resend_subscribe(); // 恢复订阅
conn_cond_.notify_one(); // 通知等待线程
} else {
if (reconnect_cnt_ != 0) {
async_reconnect(); // 自动重连
}
}
});
}
-
重连机制:
-
指数退避重连策略
-
可配置最大重连次数
-
线程安全的连接状态管理
-
void reset_deadline_timer(size_t timeout) {
deadline_.expires_from_now(std::chrono::seconds(timeout));
deadline_.async_wait([this, timeout](const asio::error_code &ec) {
if (!ec && has_connected_) {
write(0, request_type::req_res, buffer_type(0), 0); // 发送心跳
}
reset_deadline_timer(timeout); // 递归设置下一次心跳
});
}
-
心跳机制:
-
定时发送空包保持连接
-
自动重置定时器形成周期
-
连接断开时自动停止
-
4. 数据读写核心
写操作体系
void write(uint64_t req_id, request_type type,
rpc_service::buffer_type &&message, uint32_t func_id) {
// 构造消息并加入队列
if (outbox_.size() == 1) {
write(); // 直接发送
}
}
void write() {
// 组装消息头
async_write(write_buffers, [this](const asio::error_code &ec, size_t length) {
if (!ec) {
::free(outbox_.front().content.data()); // 释放内存
outbox_.pop_front();
if (!outbox_.empty()) write(); // 继续发送下一条
} else {
handle_write_error(ec); // 错误处理
}
});
}
-
特点:
-
消息队列保证发送顺序
-
零拷贝优化(移动语义)
-
异步发送不阻塞IO线程
-
读操作体系
void do_read() {
async_read_head([this](const asio::error_code &ec, size_t length) {
if (!ec) {
rpc_header *header = (rpc_header *)(head_);
if (valid_body_len(header->body_len)) {
read_body(header->req_id, header->req_type, header->body_len);
}
} else {
handle_read_error(ec);
}
});
}
void read_body(uint64_t req_id, request_type req_type, size_t body_len) {
async_read(body_len, [this, req_id, req_type, body_len](...) {
if (!ec) {
if (req_type == request_type::req_res) {
call_back(req_id, ec, {body_.data(), body_len}); // RPC响应
} else {
callback_sub(ec, {body_.data(), body_len}); // 订阅消息
}
do_read(); // 继续读下一条
} else {
handle_read_error(ec);
}
});
}
-
特点:
-
分两阶段读取(头+体)
-
自动处理不同类型消息
-
循环读取实现持续通信
-
5. 回调处理
void call_back(uint64_t req_id, const asio::error_code &ec, string_view data) {
if (client_language_ == JAVA) {
on_result_received_callback_(req_id, std::string(data)); // Java回调
} else {
if (req_id >> 63) { // 回调模式
auto call = std::move(callback_map_[req_id]);
if (!call->has_timeout()) {
call->callback(ec, data); // 执行用户回调
}
} else { // Future模式
future_map_[req_id]->set_value(req_result{data});
}
}
}
-
分发机制:
-
高位区分回调类型
-
超时检查保证及时释放
-
多语言支持统一入口
-
6. 资源管理
void clear_cache() {
// 清空发送队列
while (!outbox_.empty()) {
::free(outbox_.front().content.data());
outbox_.pop_front();
}
// 清空回调映射
callback_map_.clear();
future_map_.clear();
}
-
作用:连接断开时清理所有待处理请求
void reset_socket() {
socket_.close();
socket_ = decltype(socket_)(ios_); // 重建socket
if (!socket_.is_open()) {
socket_.open(asio::ip::tcp::v4()); // 重新打开
}
}
-
作用:重连前重置socket状态
7. 错误处理机制
void error_callback(const asio::error_code &ec) {
if (err_cb_) {
err_cb_(ec); // 调用用户自定义错误回调
}
if (enable_reconnect_) {
async_reconnect(); // 启用自动重连时尝试重新连接
}
}
-
作用:统一错误处理入口
-
流程:
-
优先执行用户注册的错误回调
-
根据自动重连标志决定是否重连
-
-
特点:
-
集中处理所有网络错误
-
避免错误处理代码重复
-
void set_default_error_callback() {
err_cb_ = [this](asio::error_code) {
async_connect(); // 默认行为是尝试重连
};
}
-
作用:设置默认错误处理行为
-
使用场景:当用户未提供自定义错误回调时使用
8. SSL/TLS安全连接
bool is_ssl() const {
#ifdef CINATRA_ENABLE_SSL
return ssl_stream_ != nullptr; // 检查SSL流对象是否存在
#else
return false; // 未启用SSL编译时始终返回false
#endif
}
-
作用:判断当前是否使用SSL连接
-
特点:通过条件编译实现跨平台支持
void handshake() {
#ifdef CINATRA_ENABLE_SSL
ssl_stream_->async_handshake(asio::ssl::stream_base::client,
[this](const asio::error_code &ec) {
if (!ec) {
// 握手成功后的初始化
has_connected_ = true;
do_read(); // 开始接收数据
resend_subscribe(); // 恢复订阅
if (has_wait_)
conn_cond_.notify_one(); // 通知等待线程
} else {
error_callback(ec); // 握手失败处理
close();
}
});
#endif
}
-
作用:执行SSL握手协议
-
流程:
-
异步发起SSL握手
-
成功则初始化连接状态
-
失败则触发错误处理
-
-
特点:完全异步的非阻塞实现
void upgrade_to_ssl() {
#ifdef CINATRA_ENABLE_SSL
if (ssl_stream_) return; // 避免重复初始化
// 创建SSL上下文
asio::ssl::context ssl_context(asio::ssl::context::sslv23);
ssl_context.set_default_verify_paths();
// 应用自定义SSL配置
if (ssl_context_callback_) {
ssl_context_callback_(ssl_context);
}
// 创建SSL流对象
ssl_stream_ = std::make_unique<asio::ssl::stream<asio::ip::tcp::socket &>>(
socket_, ssl_context);
#endif
}
-
作用:将普通连接升级为SSL连接
-
特点:
-
惰性初始化(首次需要时创建)
-
支持自定义SSL配置回调
-
显式条件编译保证安全
-
9. 异步IO操作模板
异步读操作模板
template <typename Handler>
void async_read_head(Handler handler) {
if (is_ssl()) {
// SSL模式读取
asio::async_read(*ssl_stream_, asio::buffer(head_, HEAD_LEN),
std::move(handler));
} else {
// 普通模式读取
asio::async_read(socket_, asio::buffer(head_, HEAD_LEN),
std::move(handler));
}
}
template <typename Handler>
void async_read(size_t size_to_read, Handler handler) {
if (is_ssl()) {
// SSL模式读取消息体
asio::async_read(*ssl_stream_, asio::buffer(body_.data(), size_to_read),
std::move(handler));
} else {
// 普通模式读取消息体
asio::async_read(socket_, asio::buffer(body_.data(), size_to_read),
std::move(handler));
}
}
异步写操作模板
template <typename BufferType, typename Handler>
void async_write(const BufferType &buffers, Handler handler) {
if (is_ssl()) {
// SSL模式写入
asio::async_write(*ssl_stream_, buffers, std::move(handler));
} else {
// 普通模式写入
asio::async_write(socket_, buffers, std::move(handler));
}
}
模板设计特点:
-
统一接口:为SSL和非SSL连接提供相同调用方式
-
类型安全:使用模板参数确保编译期类型检查
-
性能优化:
-
完美转发handler避免额外拷贝
-
运行时条件判断最小化
-
-
灵活性:支持任意符合ASIO要求的缓冲区类型
call_t 类
call_t
类是 RPC 客户端内部用于管理异步调用超时和回调的核心组件,使得 RPC 客户端能够可靠地管理大量并发异步调用,在保证性能的同时提供精确的超时控制。
1. 类设计基础
继承结构
-
asio::noncopyable:禁止拷贝构造和拷贝赋值,确保资源安全
-
std::enable_shared_from_this:支持返回智能指针的 shared_from_this() 方法
构造函数
call_t(asio::io_service &ios,
std::function<void(asio::error_code, string_view)> cb,
size_t timeout)
: timer_(ios), cb_(std::move(cb)), timeout_(timeout) {}
-
参数:
-
ios
:ASIO IO 服务上下文 -
cb
:用户回调函数,接收错误码和数据视图 -
timeout
:超时时间(毫秒)
-
-
初始化:
-
定时器绑定到 IO 服务
-
回调函数使用移动语义避免拷贝
-
超时时间保存备用
-
2. 核心方法实现
定时器管理
void start_timer() {
if (timeout_ == 0) return; // 0表示不启用超时
timer_.expires_from_now(std::chrono::milliseconds(timeout_));
auto self = this->shared_from_this(); // 保持对象存活
timer_.async_wait([this, self](asio::error_code ec) {
if (!ec) has_timeout_ = true; // 标记超时状态
});
}
-
机制:
-
设置定时器到期时间
-
通过 shared_from_this() 保持对象生命周期
-
异步等待超时事件
-
void cancel() {
if (timeout_ == 0) return;
asio::error_code ec;
timer_.cancel(ec); // 取消定时器
}
-
作用:在正常收到响应时取消超时检测
回调执行
void callback(asio::error_code ec, string_view data) {
cb_(ec, data); // 直接转发参数给用户回调
}
-
特点:简单透明的回调转发,不添加额外逻辑
状态检查
bool has_timeout() const {
return has_timeout_; // 返回超时标志状态
}
-
用途:供外部检查是否已触发超时
3. 生命周期管理
-
对象创建:
-
由 rpc_client 在发起异步调用时创建
-
存储于 callback_map_ 的 shared_ptr 中
-
-
存活周期:
-
从调用开始到收到响应或超时
-
通过 shared_from_this() 确保定时器回调期间对象存活
-
-
资源释放:
-
正常响应或超时后从 map 中移除
-
shared_ptr 引用计数归零时自动销毁
-