C++开发:rest_rpc服务端解析

【链接】https://github.com/qicosmos/rest_rpc

  rpc_server 的类,它位于 rest_rpc::rpc_service 命名空间下。这个类是用于实现一个基于Asio的RPC服务器的核心组件。以下是该类的主要功能和结构的解析:

主要功能

  1. 初始化与启动:通过构造函数初始化服务器,指定监听端口、线程池大小、超时设置等,并开始接受连接。
  2. 注册处理器:允许注册普通函数或成员函数作为处理程序,以响应特定类型的请求。
  3. 异步运行:提供方法来异步运行服务器(在独立线程中)或同步运行(阻塞当前线程)。
  4. 发布-订阅模式支持:允许客户端订阅某些键值,并向这些键值对应的客户端发布消息。
  5. 错误和网络异常处理:提供了设置回调函数的方法,以便对特定事件进行处理,如错误发生时或连接超时时。

核心数据成员

  • 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::mutexstd::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 支持:提供安全通信选项。
  • 灵活的回调机制:允许用户自定义错误处理、连接超时行为等。
  • 线程安全设计:对共享资源进行了良好的同步保护

改进建议

  1. 日志记录:目前注释掉的日志输出,建议启用或集成日志框架。
  2. 配置化参数:如超时时间、线程数等可以抽离为配置文件。
  3. 连接池复用:可以考虑连接复用机制提升性能。
  4. 异常安全:部分操作没有异常捕获,建议加强健壮性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值