【链接】https://github.com/qicosmos/rest_rpc
rpc_server
的类,它位于 rest_rpc::rpc_service
命名空间下。这个类是用于实现一个基于Asio的RPC服务器的核心组件。以下是该类的主要功能和结构的解析:
主要功能
- 初始化与启动:通过构造函数初始化服务器,指定监听端口、线程池大小、超时设置等,并开始接受连接。
- 注册处理器:允许注册普通函数或成员函数作为处理程序,以响应特定类型的请求。
- 异步运行:提供方法来异步运行服务器(在独立线程中)或同步运行(阻塞当前线程)。
- 发布-订阅模式支持:允许客户端订阅某些键值,并向这些键值对应的客户端发布消息。
- 错误和网络异常处理:提供了设置回调函数的方法,以便对特定事件进行处理,如错误发生时或连接超时时。
核心数据成员
io_service_pool_
:I/O服务池,用于管理后台线程执行异步操作。acceptor_
:TCP接收器,负责监听新的连接请求。conn_
:指向当前正在处理的连接的智能指针。connections_
:存储所有活动连接的映射表。router_
:路由对象,用于将接收到的消息分发到相应的处理器。signals_
:信号集,用于捕获进程信号(如SIGINT, SIGTERM)以优雅地关闭服务器。- 多个条件变量和互斥锁(如
mtx_
,sub_mtx_
),用于保护共享资源并控制线程间的同步。 - 各种回调函数(如
err_cb_
,conn_timeout_callback_
,on_net_err_callback_
),用于自定义错误处理逻辑。
关键方法
- 构造函数:配置服务器参数,并启动必要的后台线程(例如检查连接状态、处理发布/订阅逻辑)。
- do_accept():开始接受新的TCP连接。
- clean() 和 clean_sub_pub():定期清理不再活跃的连接或订阅关系。
- publish() 系列方法:向特定主题的所有订阅者发送消息。
- register_handler() 系列方法:允许动态注册处理函数。
- stop():停止服务器并释放资源。
#ifndef REST_RPC_RPC_SERVER_H_
#define REST_RPC_RPC_SERVER_H_
#include "connection.h"
#include "io_service_pool.h"
#include "router.h"
#include <condition_variable>
#include <mutex>
#include <thread>
using asio::ip::tcp;
namespace rest_rpc {
namespace rpc_service {
using rpc_conn = std::weak_ptr<connection>;
class rpc_server : private asio::noncopyable
{
public:
rpc_server(unsigned short port, size_t size, size_t timeout_seconds = 15, size_t check_seconds = 10)
: io_service_pool_(size),
acceptor_(io_service_pool_.get_io_service(),
tcp::endpoint(tcp::v4(), port)),
timeout_seconds_(timeout_seconds),
check_seconds_(check_seconds),
signals_(io_service_pool_.get_io_service())
{
do_accept();
check_thread_ = std::make_shared<std::thread>([this] { clean(); });
pub_sub_thread_ = std::make_shared<std::thread>([this] { clean_sub_pub(); });
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
do_await_stop();
}
rpc_server(unsigned short port, size_t size, ssl_configure ssl_conf, size_t timeout_seconds = 15, size_t check_seconds = 10)
: rpc_server(port, size, timeout_seconds, check_seconds)
{
#ifdef CINATRA_ENABLE_SSL
ssl_conf_ = std::move(ssl_conf);
#else
assert(false); // please add definition CINATRA_ENABLE_SSL, not allowed
// coming in this branch
#endif
}
~rpc_server() { stop(); }
void async_run() {
thd_ = std::make_shared<std::thread>([this] { io_service_pool_.run(); });
}
void run() { io_service_pool_.run(); }
template <bool is_pub = false, typename Function>
void register_handler(std::string const &name, const Function &f) {
router_.register_handler<is_pub>(name, f);
}
template <bool is_pub = false, typename Function, typename Self>
void register_handler(std::string const &name, const Function &f,
Self *self) {
router_.register_handler<is_pub>(name, f, self);
}
void
set_error_callback(std::function<void(asio::error_code, string_view)> f) {
err_cb_ = std::move(f);
}
void set_conn_timeout_callback(std::function<void(int64_t)> callback) {
conn_timeout_callback_ = std::move(callback);
}
void set_network_err_callback(
std::function<void(std::shared_ptr<connection>, std::string /*reason*/)>
on_net_err) {
on_net_err_callback_ = std::move(on_net_err);
}
template <typename T> void publish(const std::string &key, T data) {
publish(key, "", std::move(data));
}
template <typename T>
void publish_by_token(const std::string &key, std::string token, T data) {
publish(key, std::move(token), std::move(data));
}
std::set<std::string> get_token_list() {
std::unique_lock<std::mutex> lock(sub_mtx_);
return token_list_;
}
private:
void do_accept() {
conn_.reset(new connection(io_service_pool_.get_io_service(),
timeout_seconds_, router_));
conn_->set_callback([this](std::string key, std::string token,
std::weak_ptr<connection> conn) {
std::unique_lock<std::mutex> lock(sub_mtx_);
sub_map_.emplace(std::move(key) + token, conn);
if (!token.empty()) {
token_list_.emplace(std::move(token));
}
});
acceptor_.async_accept(conn_->socket(), [this](asio::error_code ec) {
if (!acceptor_.is_open()) {
return;
}
if (ec) {
// LOG(INFO) << "acceptor error: " <<
// ec.message();
} else {
#ifdef CINATRA_ENABLE_SSL
if (!ssl_conf_.cert_file.empty()) {
conn_->init_ssl_context(ssl_conf_);
}
#endif
if (on_net_err_callback_) {
conn_->on_network_error(on_net_err_callback_);
}
conn_->start();
std::unique_lock<std::mutex> lock(mtx_);
conn_->set_conn_id(conn_id_);
connections_.emplace(conn_id_++, conn_);
}
do_accept();
});
}
void clean() {
while (!stop_check_) {
std::unique_lock<std::mutex> lock(mtx_);
cv_.wait_for(lock, std::chrono::seconds(check_seconds_));
for (auto it = connections_.cbegin(); it != connections_.cend();) {
if (it->second->has_closed()) {
if (conn_timeout_callback_) {
conn_timeout_callback_(it->second->conn_id());
}
it = connections_.erase(it);
} else {
++it;
}
}
}
}
void clean_sub_pub() {
while (!stop_check_pub_sub_) {
std::unique_lock<std::mutex> lock(sub_mtx_);
sub_cv_.wait_for(lock, std::chrono::seconds(10));
for (auto it = sub_map_.cbegin(); it != sub_map_.cend();) {
auto conn = it->second.lock();
if (conn == nullptr || conn->has_closed()) {
// remove token
for (auto t = token_list_.begin(); t != token_list_.end();) {
if (it->first.find(*t) != std::string::npos) {
t = token_list_.erase(t);
} else {
++t;
}
}
it = sub_map_.erase(it);
} else {
++it;
}
}
}
}
void error_callback(const asio::error_code &ec, string_view msg) {
if (err_cb_) {
err_cb_(ec, msg);
}
}
template <typename T>
void publish(std::string key, std::string token, T data) {
{
std::unique_lock<std::mutex> lock(sub_mtx_);
if (sub_map_.empty())
return;
}
std::shared_ptr<std::string> shared_data =
get_shared_data<T>(std::move(data));
std::unique_lock<std::mutex> lock(sub_mtx_);
auto range = sub_map_.equal_range(key + token);
if (range.first != range.second) {
for (auto it = range.first; it != range.second; ++it) {
auto conn = it->second.lock();
if (conn == nullptr || conn->has_closed()) {
continue;
}
conn->publish(key + token, *shared_data);
}
} else {
error_callback(
asio::error::make_error_code(asio::error::invalid_argument),
"The subscriber of the key: " + key + " does not exist.");
}
}
template <typename T>
typename std::enable_if<std::is_assignable<std::string, T>::value,
std::shared_ptr<std::string>>::type
get_shared_data(std::string data) {
return std::make_shared<std::string>(std::move(data));
}
template <typename T>
typename std::enable_if<!std::is_assignable<std::string, T>::value,
std::shared_ptr<std::string>>::type
get_shared_data(T data) {
msgpack_codec codec;
auto buf = codec.pack(std::move(data));
return std::make_shared<std::string>(buf.data(), buf.size());
}
void do_await_stop() {
signals_.async_wait(
[this](std::error_code /*ec*/, int /*signo*/) { stop(); });
}
void stop() {
if (has_stoped_) {
return;
}
{
std::unique_lock<std::mutex> lock(mtx_);
stop_check_ = true;
cv_.notify_all();
}
check_thread_->join();
{
std::unique_lock<std::mutex> lock(sub_mtx_);
stop_check_pub_sub_ = true;
sub_cv_.notify_all();
}
pub_sub_thread_->join();
io_service_pool_.stop();
if (thd_) {
thd_->join();
}
has_stoped_ = true;
}
io_service_pool io_service_pool_;
tcp::acceptor acceptor_;
std::shared_ptr<connection> conn_;
std::shared_ptr<std::thread> thd_;
std::size_t timeout_seconds_;
std::unordered_map<int64_t, std::shared_ptr<connection>> connections_;
int64_t conn_id_ = 0;
std::mutex mtx_;
std::shared_ptr<std::thread> check_thread_;
size_t check_seconds_;
bool stop_check_ = false;
std::condition_variable cv_;
asio::signal_set signals_;
std::function<void(asio::error_code, string_view)> err_cb_;
std::function<void(int64_t)> conn_timeout_callback_;
std::function<void(std::shared_ptr<connection>, std::string)>
on_net_err_callback_ = nullptr;
std::unordered_multimap<std::string, std::weak_ptr<connection>> sub_map_;
std::set<std::string> token_list_;
std::mutex sub_mtx_;
std::condition_variable sub_cv_;
std::shared_ptr<std::thread> pub_sub_thread_;
bool stop_check_pub_sub_ = false;
ssl_configure ssl_conf_;
router router_;
std::atomic_bool has_stoped_ = {false};
};
} // namespace rpc_service
} // namespace rest_rpc
#endif // REST_RPC_RPC_SERVER_H_
基于 C++ 的 RPC 服务器实现,使用了 Boost.Asio(或 Asio)网络库来处理异步通信。整个 rpc_server
类负责管理连接、注册回调函数、发布/订阅机制以及 SSL 配置等。
类结构概览
class rpc_server : private asio::noncopyable
- 继承自
asio::noncopyable
表示该类不可复制。 - 使用私有继承,避免外部访问其基类接口。
构造函数与析构函数
构造函数
rpc_server(unsigned short port, size_t size, size_t timeout_seconds = 15,
size_t check_seconds = 10)
- 启动 IO 线程池(
io_service_pool_
),并监听指定端口。 - 初始化信号处理(捕获 SIGINT、SIGTERM 等)。
- 启动两个后台线程:
- 用于清理超时连接(
clean()
) - 用于清理失效的发布/订阅连接(
clean_sub_pub()
)
- 用于清理超时连接(
带 SSL 配置的构造函数
rpc_server(unsigned short port, size_t size, ssl_configure ssl_conf,
size_t timeout_seconds = 15, size_t check_seconds = 10)
- 调用前一个构造函数,并设置 SSL 配置。
- 如果未定义
CINATRA_ENABLE_SSL
宏,则断言失败,防止误用。
析构函数
~rpc_server()
- 调用
stop()
方法停止服务。
核心功能
1. 启动服务器
同步启动
void run()
- 在主线程中运行 IO 池。
异步启动
void async_run()
- 在子线程中运行 IO 池。
2. 注册处理函数
普通注册
template <bool is_pub = false, typename Function>
void register_handler(std::string const &name, const Function &f)
- 将 RPC 方法名与回调函数绑定。
- 支持普通函数和成员函数指针。
成员函数注册
template <bool is_pub = false, typename Function, typename Self>
void register_handler(std::string const &name, const Function &f, Self *self)
- 可以将对象方法作为 RPC 处理器。
3. 回调函数设置
错误回调
void set_error_callback(std::function<void(asio::error_code, string_view)> f)
连接超时回调
void set_conn_timeout_callback(std::function<void(int64_t)> callback)
网络错误回调
void set_network_err_callback(...)
4. 发布/订阅机制
发布消息
template <typename T> void publish(const std::string &key, T data)
template <typename T> void publish_by_token(const std::string &key,
std::string token, T data)
- 向所有订阅者广播消息。
- 可按 token 精确发送。
获取当前 token 列表
std::set<std::string> get_token_list()
连接管理
接收新连接
void do_accept()
- 异步等待客户端连接。
- 创建新的
connection
实例。 - 设置连接 ID 并加入连接列表。
清理超时连接
void clean()
- 定期检查连接是否已关闭。
- 若已关闭则从连接列表中移除。
清理无效订阅
void clean_sub_pub()
- 定期检查订阅者是否有效。
- 若无效则从订阅列表中删除。
辅助功能
停止服务
void stop()
- 停止所有后台线程。
- 关闭 IO 池。
- 清理资源。
等待终止信号
void do_await_stop()
- 监听系统信号(如 Ctrl+C)以优雅退出。
数据打包机制
数据封装成 msgpack 格式
template <typename T>
std::shared_ptr<std::string> get_shared_data(T data)
- 如果是字符串类型,直接返回。
- 否则使用 msgpack 编码为二进制字符串。
线程安全机制
- 使用
std::mutex
和std::condition_variable
保证多线程安全。 - 对
connections_
和sub_map_
等共享资源进行加锁保护。
总结:主要模块图解
+-----------------------------+
| rpc_server |
+-----------------------------+
| - io_service_pool_ | // IO 线程池
| - acceptor_ | // 接收客户端连接
| - connections_ | // 所有活跃连接
| - sub_map_ | // 订阅关系表
| - router_ | // 处理函数路由
+-----------------------------+
+-----------------------------+
| 功能模块 |
+-----------------------------+
| - do_accept() | // 接收新连接
| - clean() | // 清理超时连接
| - clean_sub_pub() | // 清理无效订阅
| - publish() | // 发布消息
| - register_handler() | // 注册 RPC 方法
| - set_*_callback() | // 设置各种回调
+-----------------------------+
优点分析
- 异步非阻塞架构:基于 Asio 的异步模型,性能高。
- 可扩展性强:支持发布/订阅模式,适合构建实时通信服务。
- SSL 支持:提供安全通信选项。
- 灵活的回调机制:允许用户自定义错误处理、连接超时行为等。
- 线程安全设计:对共享资源进行了良好的同步保护
改进建议
- 日志记录:目前注释掉的日志输出,建议启用或集成日志框架。
- 配置化参数:如超时时间、线程数等可以抽离为配置文件。
- 连接池复用:可以考虑连接复用机制提升性能。
- 异常安全:部分操作没有异常捕获,建议加强健壮性。