feat(libmsg): extract pymsg-compatible messaging library as shared lib

- Add libmsg: C++20 header-only pymsg protocol implementation
  - Msg.hpp: pmsg_send/pmsg_recv (blocking + deadline modes) + MsgClient
  - MsgServer.hpp: multi-client TCP server with select event loop + thread pool
  - AsyncQueue<T>: generic async batch processing queue
  - RpcClient/RpcServer: JSON RPC convention layer (requires boost::json)
  - Msg_Basic.hpp/cpp: exception hierarchy, constants, version
- Fix thread safety: MsgClient _sockfd/_running use std::atomic with exchange()
- Fix deadline recv: client call() uses shared deadline for header+payload
- Fix graceful shutdown: thread pool drains queued tasks before exit
- Fix pmsg_send size check: enforce 64MiB limit (was 4GiB no-op check)
- Align AsyncQueue batch semantics with Python: batch_size is threshold
- Refactor gateway/LogC.hpp to use libmsg AsyncQueue<T>
- Update gateway Makefile to link -lus_msg, DbC.hpp to use <us/Msg.hpp>
- Add libmsg to _inst.sh build order
This commit is contained in:
qing committed 2026-08-06 23:14:13 +08:00
1 parent bbb88a96f8
commit 81ba942b8e
17 files changed
+2475 -482

No files matched your search

+289
View File
@@ -0,0 +1,289 @@
#pragma once
/** AsyncQueue — 通用异步批量发送队列(C++ 版,对应 Python 版 pymsg/pymsg/async_queue.py)
*
* 提供内存队列 + 后台线程的异步批量处理机制,适用于日志上报、指标采集、事件通知等
* 非关键路径场景。业务线程 push() 非阻塞立即返回,后台线程攒批处理(批量阈值或超时),
* 失败时通过错误回调通知,绝不阻塞或影响主业务流程。
*
* 设计要点(与 Python AsyncRpcQueue 对齐):
* - 非阻塞入队:push() 立即返回,不等待网络 I/O
* - 批量发送:攒到 batch_size 条或超过 flush_interval 秒自动 flush
* - 背压保护:max_queue_size 限制内存队列长度,满时丢最旧数据并计数
* - 优雅关闭:shutdown() flush 剩余数据后停止后台线程
* - RAII:析构自动 shutdown()
* - 可插拔:通过 Handler 回调自定义批量处理逻辑(RPC 调用、写文件、发消息等)
*
* Usage:
* #include <us/AsyncQueue.hpp>
*
* struct LogEntry { std::string service; std::string msg; };
*
* pmc::net::AsyncQueue<LogEntry> queue(
* [](std::vector<LogEntry>&& batch) {
* for (auto& e : batch) { send_to_server(e); }
* },
* {50, 2.0, 10000, 0.5},
* [](const std::exception& e, std::vector<LogEntry>&&) {
* std::cerr << "[queue] send failed: " << e.what() << std::endl;
* }
* );
* queue.start();
* queue.push(LogEntry{"gateway", "started"});
* queue.shutdown();
*/
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <exception>
#include <functional>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
namespace pmc {
namespace net {
// ============================================================================
// AsyncQueue 配置
// ============================================================================
struct AsyncQueueConfig {
size_t batch_size = 100; ///< 批量发送阈值,达到此数量立即 flush
double flush_interval = 2.0; ///< 定时 flush 间隔(秒)
size_t max_queue_size = 10000; ///< 队列最大长度,0 表示无上限;超过时丢最旧数据
double poll_timeout = 0.1; ///< 后台线程轮询超时(秒),越小关闭响应越快但CPU占用略高
};
// ============================================================================
// AsyncQueue<T>
// ============================================================================
/** 通用异步批量发送队列。
*
* @tparam T 队列元素类型,需支持移动构造(push 时会被 std::move 到 batch 中)。
*/
template <typename T>
class AsyncQueue {
public:
/// 批量处理回调:接收一个元素批次的右值引用,处理完后 batch 被销毁。
using Handler = std::function<void(std::vector<T>&& batch)>;
/// 错误回调:接收异常和失败的批次(可用于重试或记录)。
using ErrorHandler = std::function<void(const std::exception& e, std::vector<T>&& batch)>;
// ---- 构造 / 析构 -------------------------------------------------------
explicit AsyncQueue(Handler handler,
AsyncQueueConfig config = {},
ErrorHandler on_error = {})
: _handler(std::move(handler)),
_config(config),
_on_error(std::move(on_error)),
_running(false),
_stop(false),
_wakeup(false),
_dropped(0) {}
/// 析构函数:自动 shutdown(),保证队列中剩余数据被 flush。
~AsyncQueue() { shutdown(); }
AsyncQueue(const AsyncQueue&) = delete;
AsyncQueue& operator=(const AsyncQueue&) = delete;
// ---- 生命周期 ----------------------------------------------------------
/// 启动后台发送线程。幂等:已启动时无操作。
void start() {
std::lock_guard<std::mutex> lock(_mtx);
if (_running) return;
_stop = false;
_wakeup = false;
_running = true;
_thread = std::thread(&AsyncQueue::_run, this);
}
/// 优雅关闭:flush 剩余数据后停止后台线程。幂等。
void shutdown() {
{
std::lock_guard<std::mutex> lock(_mtx);
if (!_running) return;
_stop = true;
_wakeup = true;
}
_cv.notify_all();
if (_thread.joinable()) {
_thread.join();
}
// 线程结束后,flush 队列中剩余的所有数据
_flush_all();
{
std::lock_guard<std::mutex> lock(_mtx);
_running = false;
}
}
// ---- 入队 --------------------------------------------------------------
/** 非阻塞入队一条数据。
*
* 若队列已关闭(shutdown 后),则丢弃数据并返回 false。
* 若队列长度达到 max_queue_size,则丢弃最旧的数据并递增 dropped 计数。
*
* @return true 入队成功;false 队列已关闭。
*/
bool push(T item) {
{
std::lock_guard<std::mutex> lock(_mtx);
if (_stop) return false;
if (_config.max_queue_size > 0 && _queue.size() >= _config.max_queue_size) {
_queue.pop_front();
++_dropped;
}
_queue.push_back(std::move(item));
_wakeup = true;
}
_cv.notify_one();
return true;
}
// ---- 手动 flush --------------------------------------------------------
/// 立即触发一次 flush,在调用线程同步处理当前队列中的所有数据。
/// 对齐 Python:flush() 直接在调用线程执行 _send_batch(),阻塞直到处理完成。
void flush() {
_send_batch();
}
// ---- 状态查询 ----------------------------------------------------------
/// 当前队列中待处理的数据量。
size_t pending() const {
std::lock_guard<std::mutex> lock(_mtx);
return _queue.size();
}
/// 因队列满而丢弃的数据总数。
size_t dropped() const {
return _dropped.load();
}
/// 队列是否正在运行(已 start 且未 shutdown)。
bool is_running() const {
std::lock_guard<std::mutex> lock(_mtx);
return _running;
}
private:
// ---- 后台线程主循环(对齐 Python AsyncRpcQueue._run)--------------------
void _run() {
auto last_flush = std::chrono::steady_clock::now();
while (true) {
bool should_stop = false;
{
std::unique_lock<std::mutex> lock(_mtx);
_cv.wait_for(lock,
std::chrono::duration<double>(_config.poll_timeout),
[this] { return _stop || _wakeup; });
_wakeup = false; // 消费唤醒标志
should_stop = _stop;
}
const auto now = std::chrono::steady_clock::now();
const bool due = std::chrono::duration<double>(now - last_flush).count()
>= _config.flush_interval;
size_t qsize = 0;
{
std::lock_guard<std::mutex> lock(_mtx);
qsize = _queue.size();
}
// flush 条件:达到批量阈值 / 超时且有数据 / 停止时还有数据
if (qsize >= _config.batch_size || (due && qsize > 0) || (should_stop && qsize > 0)) {
_send_batch();
last_flush = std::chrono::steady_clock::now();
}
if (should_stop) break;
}
}
// ---- 取所有待处理数据并发送(对齐 Python:batch_size 是触发阈值,不是上限)-
void _send_batch() {
std::vector<T> batch;
{
std::lock_guard<std::mutex> lock(_mtx);
if (_queue.empty()) return;
batch.reserve(_queue.size());
while (!_queue.empty()) {
batch.push_back(std::move(_queue.front()));
_queue.pop_front();
}
}
try {
_handler(std::move(batch));
} catch (const std::exception& e) {
if (_on_error) {
try {
_on_error(e, std::move(batch));
} catch (...) {
// 错误回调本身不应传播异常
}
}
}
}
// ---- 关闭时 flush 所有剩余数据 ----------------------------------------
void _flush_all() {
while (true) {
std::vector<T> batch;
{
std::lock_guard<std::mutex> lock(_mtx);
if (_queue.empty()) break;
batch.reserve(_queue.size());
while (!_queue.empty()) {
batch.push_back(std::move(_queue.front()));
_queue.pop_front();
}
}
try {
_handler(std::move(batch));
} catch (const std::exception& e) {
if (_on_error) {
try {
_on_error(e, std::move(batch));
} catch (...) {}
}
}
}
}
// ---- 成员 --------------------------------------------------------------
Handler _handler;
AsyncQueueConfig _config;
ErrorHandler _on_error;
mutable std::mutex _mtx;
std::condition_variable _cv;
std::deque<T> _queue;
std::thread _thread;
bool _running;
bool _stop;
bool _wakeup; ///< 唤醒标志(push/shutdown 置 true,后台线程消费后置 false)
std::atomic<size_t> _dropped;
};
} // namespace net
} // namespace pmc
+462
View File
@@ -0,0 +1,462 @@
#pragma once
/** pymsg C++ 实现 — Length-prefixed variable-length socket message framing.
*
* Protocol: [4-byte big-endian uint32_t length][UTF-8 payload]
*
* 对应 Python 版 pymsg/pymsg/__init__.py,提供:
* - pmsg_send() / pmsg_recv() 底层协议收发函数
* - MsgClient 短连接 send / call / 长连接 listen 客户端
*
* Usage:
* #include <us/Msg.hpp>
*
* pmc::net::MsgClient client("127.0.0.1", 8005, "gateway");
* std::string resp = client.call(R"({"sql":"SELECT 1"})", 5.0);
* client.send("hello");
*/
#include <us/Msg_Basic.hpp>
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <functional>
#include <string>
#include <thread>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
// macOS 没有 MSG_NOSIGNAL,用 0 替代(内核不会发 SIGPIPE)
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
namespace pmc {
namespace net {
// ============================================================================
// Low-level protocol: 4-byte big-endian length prefix + UTF-8 payload
// ============================================================================
namespace detail {
using namespace msg_constants;
// ---- 4-byte big-endian 编码 / 解码 ----------------------------------------
inline void writeUint32BE(char *buf, uint32_t val) noexcept {
buf[0] = static_cast<char>((val >> 24) & 0xFFu);
buf[1] = static_cast<char>((val >> 16) & 0xFFu);
buf[2] = static_cast<char>((val >> 8) & 0xFFu);
buf[3] = static_cast<char>(val & 0xFFu);
}
inline uint32_t readUint32BE(const char *buf) noexcept {
return (static_cast<uint32_t>(static_cast<unsigned char>(buf[0])) << 24) |
(static_cast<uint32_t>(static_cast<unsigned char>(buf[1])) << 16) |
(static_cast<uint32_t>(static_cast<unsigned char>(buf[2])) << 8) |
(static_cast<uint32_t>(static_cast<unsigned char>(buf[3])));
}
// ---- socket 超时设置 ------------------------------------------------------
inline void setSockTimeout(int sockfd, double seconds) {
struct timeval tv {};
tv.tv_sec = static_cast<time_t>(seconds);
tv.tv_usec =
static_cast<long>((seconds - static_cast<double>(tv.tv_sec)) * 1'000'000.0);
::setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
::setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
}
// ---- 精确读取 n 字节 ------------------------------------------------------
inline void recvExact(int sockfd, char *buf, size_t n) {
size_t received = 0;
while (received < n) {
ssize_t r = ::recv(sockfd, buf + received, n - received, 0);
if (r == 0) {
throw MsgConnectionException(
"connection closed: expected " + std::to_string(n) +
" bytes, got " + std::to_string(received));
}
if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
throw MsgTimeoutException("recv timed out");
}
throw MsgConnectionException(
"recv error: " + std::string(::strerror(errno)));
}
received += static_cast<size_t>(r);
}
}
// ---- deadline 模式精确读取 n 字节(总超时,防 Slowloris)-------------------
// 与 Python 版 _recv_exact 对齐:每次 select 前按剩余时间收紧超时,
// header+payload 共享同一个 deadline,总接收时间不超过 timeout_sec。
inline void recvExactDeadline(int sockfd, char *buf, size_t n,
const std::chrono::steady_clock::time_point& deadline) {
size_t received = 0;
while (received < n) {
auto now = std::chrono::steady_clock::now();
if (now >= deadline) {
throw MsgTimeoutException("recv timed out");
}
auto remaining_dur = std::chrono::duration_cast<std::chrono::microseconds>(deadline - now);
double remaining = static_cast<double>(remaining_dur.count()) / 1'000'000.0;
if (remaining < 0.001) remaining = 0.001; // 最小 1ms
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
struct timeval tv;
tv.tv_sec = static_cast<time_t>(remaining);
tv.tv_usec = static_cast<long>((remaining - tv.tv_sec) * 1'000'000.0);
int ready = ::select(sockfd + 1, &rfds, nullptr, nullptr, &tv);
if (ready < 0) {
if (errno == EINTR) continue;
throw MsgConnectionException(std::string("select error: ") + ::strerror(errno));
}
if (ready == 0) {
throw MsgTimeoutException("recv timed out");
}
ssize_t r = ::recv(sockfd, buf + received, n - received, 0);
if (r == 0) {
throw MsgConnectionException(
"connection closed: expected " + std::to_string(n) +
" bytes, got " + std::to_string(received));
}
if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
throw MsgConnectionException(
"recv error: " + std::string(::strerror(errno)));
}
received += static_cast<size_t>(r);
}
}
// deadline 模式接收完整 pymsg 帧(header + payload)
inline std::string recvFrameDeadline(int sockfd, double timeout_sec) {
const auto deadline = std::chrono::steady_clock::now()
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::duration<double>(timeout_sec));
char header[kHeaderSize];
recvExactDeadline(sockfd, header, kHeaderSize, deadline);
uint32_t length = readUint32BE(header);
if (length > kMaxMsgSize) {
throw MsgException("message too large: " + std::to_string(length));
}
std::string payload(static_cast<size_t>(length), '\0');
if (length > 0) {
recvExactDeadline(sockfd, payload.data(), length, deadline);
}
return payload;
}
// ---- 完整发送 -------------------------------------------------------------
inline void sendAll(int sockfd, const char *data, size_t len) {
size_t sent = 0;
while (sent < len) {
ssize_t w =
::send(sockfd, data + sent, len - sent, MSG_NOSIGNAL);
if (w < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
throw MsgTimeoutException("send timed out");
}
throw MsgConnectionException(
"send error: " + std::string(::strerror(errno)));
}
sent += static_cast<size_t>(w);
}
}
// ---- 短连接 helper:connect → action → close ------------------------------
inline int openConnection(const std::string& addr, int port, double timeout) {
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
throw MsgConnectionException(
"socket() failed: " + std::string(::strerror(errno)));
}
struct sockaddr_in serv_addr {};
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(static_cast<uint16_t>(port));
if (::inet_pton(AF_INET, addr.c_str(), &serv_addr.sin_addr) <= 0) {
::close(fd);
throw MsgConnectionException("invalid address: " + addr);
}
setSockTimeout(fd, timeout);
if (::connect(fd, reinterpret_cast<struct sockaddr *>(&serv_addr),
sizeof(serv_addr)) < 0) {
int saved_errno = errno;
::close(fd);
std::string err_msg = "could not connect to " + addr + ":" +
std::to_string(port) + " - " + ::strerror(saved_errno);
if (saved_errno == ECONNREFUSED) {
throw MsgConnectionRefusedException(err_msg);
}
throw MsgConnectionException(err_msg);
}
return fd;
}
} // namespace detail
// ============================================================================
// 底层协议函数:pmsg_send / pmsg_recv
// ============================================================================
/// 以 pymsg 协议发送一条字符串消息(长度前缀 + UTF-8 载荷)。
inline void pmsg_send(int sockfd, const std::string &data) {
size_t payload_size = data.size();
if (payload_size > msg_constants::kMaxMsgSize) {
throw MsgException(
"payload too large: " + std::to_string(payload_size) +
" bytes (max " + std::to_string(msg_constants::kMaxMsgSize) + ")");
}
char header[detail::kHeaderSize];
detail::writeUint32BE(header, static_cast<uint32_t>(payload_size));
detail::sendAll(sockfd, header, detail::kHeaderSize);
detail::sendAll(sockfd, data.c_str(), payload_size);
}
/// 以 pymsg 协议接收一条字符串消息(阻塞模式,依赖 socket 已设置的超时)。
/// @throws MsgConnectionException 连接在收全消息前断开。
/// @throws MsgTimeoutException 等待超时。
inline std::string pmsg_recv(int sockfd) {
char header[detail::kHeaderSize];
detail::recvExact(sockfd, header, detail::kHeaderSize);
uint32_t length = detail::readUint32BE(header);
if (length > msg_constants::kMaxMsgSize) { // 安全上限 64 MiB
throw MsgException("message too large: " + std::to_string(length));
}
std::string payload(static_cast<size_t>(length), '\0');
detail::recvExact(sockfd, payload.data(), length);
return payload;
}
/// 以 pymsg 协议接收一条字符串消息(deadline 模式,总超时不超过 timeout_sec)。
/// header 和 payload 共享同一个 deadline,防止 Slowloris 攻击。
/// 不依赖 socket 的 SO_RCVTIMEO,使用 select() 实现精确超时控制。
/// @param timeout_sec 总超时秒数
/// @throws MsgConnectionException 连接在收全消息前断开。
/// @throws MsgTimeoutException 总超时到期。
inline std::string pmsg_recv(int sockfd, double timeout_sec) {
return detail::recvFrameDeadline(sockfd, timeout_sec);
}
// ============================================================================
// MsgClient
// ============================================================================
/// 基于 pymsg 协议的 TCP 客户端。
///
/// 支持三种操作模式:
/// - send() 短连接发送一条消息(不等待回复),消息带 name 前缀。
/// - call() 短连接发送 + 阻塞等待回复(RPC 模式),原始消息不添加前缀。
/// - listen() 基于 connect() 维持的长连接,持续接收消息并回调。
class MsgClient : public Msg_Basic {
public:
MsgClient(const std::string &addr, int port, const std::string &name)
: _addr(addr), _port(port), _name(name), _sockfd(-1), _running(false) {}
~MsgClient() { stop(); }
// ---- 连接 ---------------------------------------------------------------
/// 创建 TCP 套接字并连接到服务器(长连接,供 listen() 使用)。
void connect() {
int fd = detail::openConnection(_addr, _port, /*timeout=*/static_cast<double>(msg_constants::kDefaultTimeout));
// 如果已有旧连接,先关闭
int old = _sockfd.exchange(fd);
if (old >= 0) {
::shutdown(old, SHUT_RDWR);
::close(old);
}
}
// ---- 发送单条消息(短连接,带名字前缀)----------------------------------
/// 短连接发送一条消息到服务器,消息格式为 ``"name: msg"``。
/// 不等待回复。短连接(每次创建新 socket,发完即关)。
void send(const std::string &msg) {
int fd = detail::openConnection(_addr, _port, /*timeout=*/static_cast<double>(msg_constants::kSendTimeout));
try {
pmsg_send(fd, _name + ": " + msg);
::close(fd);
} catch (...) {
::close(fd);
throw;
}
}
// ---- 请求-应答(短连接,RPC 模式)---------------------------------------
/// 短连接发送一条消息,阻塞等待并返回一条回复。
///
/// 不会给消息添加 ``name:`` 前缀(适用于 RPC/API 调用)。
///
/// @param msg 要发送的原始字符串。
/// @param timeout 超时秒数,默认 10.0。
/// @param retry_on_refused 目标服务未启动(ECONNREFUSED)时是否阻塞重试,
/// 直到连接成功。适用于启动顺序不确定的服务发现场景。
/// 默认 false(连接拒绝立即抛异常)。
/// @param retry_interval 重试间隔秒数,默认 1.0。
/// @returns 服务器返回的字符串。
std::string call(const std::string &msg, double timeout = static_cast<double>(msg_constants::kDefaultTimeout),
bool retry_on_refused = false,
double retry_interval = 1.0) {
if (!retry_on_refused) {
// 快速路径:不重试,直接执行一次
int fd = detail::openConnection(_addr, _port, timeout);
try {
pmsg_send(fd, msg);
// deadline 模式 recv:header+payload 共享总超时,防止总等待时间翻倍
std::string resp = pmsg_recv(fd, timeout);
::close(fd);
return resp;
} catch (...) {
::close(fd);
throw;
}
}
// 重试路径:仅对 ECONNREFUSED 重试,其他错误立即抛出
int attempt = 0;
auto last_log = std::chrono::steady_clock::now() -
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::duration<double>(msg_constants::kRetryLogEvery));
while (true) {
try {
int fd = detail::openConnection(_addr, _port, timeout);
try {
pmsg_send(fd, msg);
std::string resp = pmsg_recv(fd, timeout);
::close(fd);
return resp;
} catch (...) {
::close(fd);
throw;
}
} catch (const MsgConnectionRefusedException &) {
++attempt;
auto now = std::chrono::steady_clock::now();
double elapsed = std::chrono::duration<double>(now - last_log).count();
if (elapsed >= msg_constants::kRetryLogEvery || attempt == 1) {
std::fprintf(stderr,
"[pymsg] 等待服务 %s:%d 就绪... (已重试 %d 次)\n",
_addr.c_str(), _port, attempt);
std::fflush(stderr);
last_log = now;
}
std::this_thread::sleep_for(
std::chrono::duration<double>(retry_interval));
}
// 其他异常(MsgTimeoutException、普通MsgConnectionException等)不捕获,直接向上抛出
}
}
// ---- 持续监听(长连接,阻塞回调模式)-----------------------------------
/// 在长连接上持续接收消息并通过回调处理。
///
/// 阻塞当前线程直到 stop() 被调用或连接断开。
/// 必须先调用 connect()。
///
/// @param on_message 回调 ``void(const std::string &msg)``。
void listen(std::function<void(const std::string &)> on_message) {
int fd = _sockfd.load();
if (fd < 0) {
throw MsgException("must call connect() before listen()");
}
_running = true;
while (_running.load()) {
// 使用局部 fd,避免与 stop() 竞争导致 FD_SET(-1) UB
// 若 stop() 已将 _sockfd 置为 -1,下次循环 _running=false 退出
int cur_fd = _sockfd.load();
if (cur_fd < 0) break;
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(cur_fd, &readfds);
struct timeval tv {};
tv.tv_sec = 0;
tv.tv_usec = msg_constants::kListenPollMs; // 100 ms 轮询间隔
int ready =
::select(cur_fd + 1, &readfds, nullptr, nullptr, &tv);
if (ready < 0) {
if (errno == EINTR) continue;
break; // EBADF (socket 被 stop() 关闭) 或其他致命错误
}
if (ready == 0) continue; // timeout, loop around
try {
std::string text = pmsg_recv(cur_fd);
on_message(std::move(text));
} catch (const MsgConnectionException &) {
break; // 连接断开,退出循环
} catch (const MsgTimeoutException &) {
continue; // 读超时但连接仍在,继续等待
}
}
// 原子地认领 fd 并关闭,防止与 stop() double-close
int close_fd = _sockfd.exchange(-1);
if (close_fd >= 0) {
::shutdown(close_fd, SHUT_RDWR);
::close(close_fd);
}
}
// ---- 停止 ---------------------------------------------------------------
/// 设置停止标志并关闭套接字。可从任意线程调用。
void stop() noexcept {
_running = false;
// 原子地认领 fd 并关闭,防止与 listen() double-close
int fd = _sockfd.exchange(-1);
if (fd >= 0) {
::shutdown(fd, SHUT_RDWR); // 唤醒阻塞在 recv/select 上的线程
::close(fd);
}
}
// ---- 访问器 -------------------------------------------------------------
const std::string &addr() const noexcept { return _addr; }
int port() const noexcept { return _port; }
const std::string &name() const noexcept { return _name; }
private:
std::string _addr;
int _port;
std::string _name;
std::atomic<int> _sockfd;
std::atomic<bool> _running;
};
} // namespace net
} // namespace pmc
+528
View File
@@ -0,0 +1,528 @@
#pragma once
/** MsgServer — 基于 pymsg 协议的多客户端 TCP 服务器(C++ 版,对应 Python 版 pymsg.MsgServer)
*
* 提供 select-based 事件循环 + 可选线程池并发处理,支持 reply()/broadcast() 线程安全发送。
*
* 设计要点(与 Python 版对齐):
* - select 事件循环,支持多客户端并发连接
* - workers=0 同步模式(回调在主线程执行),workers>0 线程池并发模式
* - 同一连接消息严格串行:回调执行期间暂停该连接 select 监听
* - reply()/broadcast() 经每连接发送锁串行化,防止帧交错
* - max_connections 限制活跃连接数,防止连接耗尽攻击
* - max_queue_size 限制线程池任务队列深度,慢 handler 时断开连接防 OOM
* - deadline 模式 recv 超时,防 Slowloris 攻击
* - stop() 可从任意线程(包括回调内部)安全调用,优雅关闭
* - worker 线程出错仅标记 failed,主循环统一清理连接,避免 fd 并发操作
*
* Usage:
* #include <us/MsgServer.hpp>
*
* pmc::net::MsgServer server("0.0.0.0", 8003, {.workers=4});
* server.start([&](int fd, const std::string& addr, const std::string& msg) {
* server.reply(fd, "echo: " + msg); // 回复发送者
* server.broadcast(addr + ": " + msg, fd); // 广播给其他人
* });
*/
#include <us/Msg_Basic.hpp>
#include <us/Msg.hpp>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstring>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
namespace pmc {
namespace net {
// ============================================================================
// MsgServer 配置
// ============================================================================
struct MsgServerConfig {
int workers = 0; ///< 线程池大小;0 = 同步模式(回调在主线程执行)
double recv_timeout = 10.0; ///< 接收单条消息的总超时(秒),防 Slowloris
int max_connections = 1000; ///< 最大并发连接数,超过则拒绝新连接
int max_queue_size = 1000; ///< 线程池任务队列上限(workers>0 时),满时断开连接
int listen_backlog = 10; ///< listen() backlog
double poll_timeout = 0.1; ///< select 轮询超时(秒),影响关闭响应速度
};
// ============================================================================
// MsgServer
// ============================================================================
/// 基于 pymsg 长度前缀协议的多客户端 TCP 服务器。
class MsgServer {
public:
/// 消息回调签名:(客户端 fd, 客户端地址 "ip:port", 消息内容)
using MessageCallback = std::function<void(int sockfd, const std::string& addr, const std::string& message)>;
explicit MsgServer(const std::string& addr, int port, MsgServerConfig config = {})
: _addr(addr), _port(port), _config(config), _server_fd(-1), _running(false) {}
~MsgServer() { stop(); }
MsgServer(const MsgServer&) = delete;
MsgServer& operator=(const MsgServer&) = delete;
// ---- 启动(阻塞当前线程)------------------------------------------------
/** 启动服务器,阻塞当前线程直到 stop() 被调用或发生致命错误。
*
* @param on_message 消息回调,签名 void(int fd, const string& addr, const string& msg)。
* - 同步模式(workers=0):回调在主线程执行,同一连接消息天然串行。
* - 线程池模式(workers>0):回调提交到线程池并发执行;同一连接
* 上的消息仍严格串行(回调完成前该连接暂停 select 监听)。
* 回调内可安全调用 reply()/broadcast()/stop()。
*/
void start(MessageCallback on_message) {
_on_message = std::move(on_message);
_running = true;
// 创建监听 socket
_server_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_server_fd < 0) {
throw MsgConnectionException(std::string("socket() failed: ") + ::strerror(errno));
}
int optval = 1;
::setsockopt(_server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
struct sockaddr_in serv_addr {};
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(static_cast<uint16_t>(_port));
if (::inet_pton(AF_INET, _addr.c_str(), &serv_addr.sin_addr) <= 0) {
::close(_server_fd);
_server_fd = -1;
throw MsgConnectionException("invalid bind address: " + _addr);
}
if (::bind(_server_fd, reinterpret_cast<struct sockaddr*>(&serv_addr), sizeof(serv_addr)) < 0) {
int saved_errno = errno;
::close(_server_fd);
_server_fd = -1;
throw MsgConnectionException(std::string("bind() failed: ") + ::strerror(saved_errno));
}
if (::listen(_server_fd, _config.listen_backlog) < 0) {
int saved_errno = errno;
::close(_server_fd);
_server_fd = -1;
throw MsgConnectionException(std::string("listen() failed: ") + ::strerror(saved_errno));
}
// 启动线程池(如果配置了 workers)
std::vector<std::thread> pool_threads;
if (_config.workers > 0) {
_pool_running = true;
pool_threads.reserve(_config.workers);
for (int i = 0; i < _config.workers; ++i) {
pool_threads.emplace_back(&MsgServer::_worker_loop, this);
}
}
// 主事件循环
try {
_event_loop();
} catch (...) {
// 确保线程池和连接被清理
_running = false;
_shutdown_pool(pool_threads);
_close_all_clients();
if (_server_fd >= 0) { ::close(_server_fd); _server_fd = -1; }
throw;
}
_shutdown_pool(pool_threads);
_close_all_clients();
if (_server_fd >= 0) { ::close(_server_fd); _server_fd = -1; }
}
// ---- 停止 --------------------------------------------------------------
/// 停止服务器(可从任意线程调用,包括回调内部)。
/// 关闭监听 socket 以解除 select 阻塞,事件循环退出前清理所有连接。
void stop() noexcept {
bool expected = true;
if (!_running.compare_exchange_strong(expected, false)) return;
if (_server_fd >= 0) {
::shutdown(_server_fd, SHUT_RDWR);
::close(_server_fd);
_server_fd = -1;
}
}
// ---- 发送(线程安全)----------------------------------------------------
/** 向指定客户端发送一条应答消息(线程安全)。
*
* 经每连接发送锁串行化,workers>0 时可在回调线程安全调用。
* 如果连接已断开则静默忽略(fail-safe,不抛异常)。
*/
void reply(int sockfd, const std::string& msg) {
std::shared_ptr<std::mutex> send_lock;
{
std::lock_guard<std::mutex> lock(_clients_mtx);
auto it = _send_locks.find(sockfd);
if (it == _send_locks.end()) return; // 连接已断开
send_lock = it->second;
}
std::lock_guard<std::mutex> lock(*send_lock);
try {
pmsg_send(sockfd, msg);
} catch (const MsgException&) {
// 连接可能已断开,静默忽略
}
}
/** 向所有已连接客户端广播消息(线程安全)。
*
* @param msg 要广播的消息。
* @param except_fd 排除的 fd(通常是发送者自己),默认 -1 表示不排除。
*/
void broadcast(const std::string& msg, int except_fd = -1) {
// 先拷贝一份 fd 列表,避免持锁期间发送
std::vector<std::pair<int, std::shared_ptr<std::mutex>>> targets;
{
std::lock_guard<std::mutex> lock(_clients_mtx);
targets.reserve(_clients.size());
for (auto& [fd, addr] : _clients) {
if (fd == except_fd) continue;
auto it = _send_locks.find(fd);
if (it != _send_locks.end()) {
targets.emplace_back(fd, it->second);
}
}
}
for (auto& [fd, send_lock] : targets) {
std::lock_guard<std::mutex> slock(*send_lock);
try {
pmsg_send(fd, msg);
} catch (const MsgException&) {
// 发送失败,标记为 failed 由主循环清理
std::lock_guard<std::mutex> flk(_failed_mtx);
_failed.insert(fd);
}
}
}
// ---- 状态查询 ----------------------------------------------------------
/// 当前已连接客户端数量。
size_t client_count() const {
std::lock_guard<std::mutex> lock(_clients_mtx);
return _clients.size();
}
private:
// ---- 内部:线程池 -------------------------------------------------------
struct Task {
int fd;
std::string addr;
std::string message;
};
void _worker_loop() {
while (true) {
Task task;
bool has_task = false;
{
std::unique_lock<std::mutex> lock(_pool_mtx);
_pool_cv.wait(lock, [this] { return !_pool_running || !_pool_queue.empty(); });
if (!_pool_queue.empty()) {
task = std::move(_pool_queue.front());
_pool_queue.pop();
has_task = true;
} else if (!_pool_running) {
return; // 队列空且已停止,退出
}
}
if (!has_task) continue;
// 执行回调
bool failed = false;
try {
_on_message(task.fd, task.addr, std::move(task.message));
} catch (const MsgConnectionException&) {
failed = true;
} catch (const std::exception& e) {
std::fprintf(stderr, "[MsgServer] callback error on fd=%d: %s\n",
task.fd, e.what());
std::fflush(stderr);
failed = true;
} catch (...) {
failed = true;
}
if (failed) {
std::lock_guard<std::mutex> flk(_failed_mtx);
_failed.insert(task.fd);
}
// 清除 busy 标记,恢复 select 监听
{
std::lock_guard<std::mutex> lock(_busy_mtx);
_busy.erase(task.fd);
}
}
}
void _shutdown_pool(std::vector<std::thread>& pool_threads) {
// 停止接收新任务,唤醒所有 worker 排空队列后退出
_pool_running = false;
_pool_cv.notify_all();
for (auto& t : pool_threads) {
if (t.joinable()) t.join();
}
pool_threads.clear();
// worker 已排空队列,此时 busy 集合中的任务都已完成
// 任何仍标记为 failed 的连接由 _close_all_clients 统一清理
}
// ---- 内部:主事件循环 ---------------------------------------------------
void _event_loop() {
while (_running) {
// 清理 failed 连接
_reap_failed();
// 构建 fd_set:server_fd + 非 busy 的客户端 fd
fd_set readfds, exceptfds;
FD_ZERO(&readfds);
FD_ZERO(&exceptfds);
int maxfd = -1;
if (_server_fd >= 0) {
FD_SET(_server_fd, &readfds);
maxfd = _server_fd;
}
std::vector<int> watched_clients;
{
std::lock_guard<std::mutex> clk(_clients_mtx);
std::lock_guard<std::mutex> blk(_busy_mtx);
watched_clients.reserve(_clients.size());
for (auto& [fd, addr] : _clients) {
if (_busy.count(fd)) continue; // 回调处理中,暂停监听
FD_SET(fd, &readfds);
FD_SET(fd, &exceptfds);
if (fd > maxfd) maxfd = fd;
watched_clients.push_back(fd);
}
}
if (maxfd < 0) {
// 没有任何 fd 可监听(server_fd 已关闭且无客户端),退出
break;
}
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = static_cast<long>(_config.poll_timeout * 1'000'000.0);
int ready = ::select(maxfd + 1, &readfds, nullptr, &exceptfds, &tv);
if (ready < 0) {
if (errno == EINTR) continue;
break; // EBADF 等致命错误
}
if (ready == 0) continue;
// 处理异常 fd
for (int fd : watched_clients) {
if (FD_ISSET(fd, &exceptfds)) {
_remove_client(fd);
}
}
// 处理新连接
if (_server_fd >= 0 && FD_ISSET(_server_fd, &readfds)) {
_accept_client();
}
// 处理客户端消息
for (int fd : watched_clients) {
if (FD_ISSET(fd, &readfds)) {
// 检查 fd 是否仍在客户端列表中(可能已被异常处理移除)
{
std::lock_guard<std::mutex> lock(_clients_mtx);
if (!_clients.count(fd)) continue;
}
_handle_client(fd);
}
}
}
}
void _accept_client() {
struct sockaddr_in client_addr {};
socklen_t addrlen = sizeof(client_addr);
int client_fd = ::accept(_server_fd, reinterpret_cast<struct sockaddr*>(&client_addr), &addrlen);
if (client_fd < 0) {
// EINVAL/EBADF: server_fd 已被 stop() 关闭;其他错误忽略
return;
}
if (client_fd >= FD_SETSIZE) { ::close(client_fd); return; }
// 检查连接数上限
{
std::lock_guard<std::mutex> lock(_clients_mtx);
if (static_cast<int>(_clients.size()) >= _config.max_connections) {
::close(client_fd);
return;
}
char ip_str[INET_ADDRSTRLEN];
::inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
std::string addr = std::string(ip_str) + ":" + std::to_string(ntohs(client_addr.sin_port));
_clients[client_fd] = addr;
_send_locks[client_fd] = std::make_shared<std::mutex>();
}
// 禁用 Nagle 算法,降低小包延迟
int flag = 1;
::setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
}
void _handle_client(int fd) {
std::string addr;
{
std::lock_guard<std::mutex> lock(_clients_mtx);
auto it = _clients.find(fd);
if (it == _clients.end()) return;
addr = it->second;
}
std::string message;
try {
// deadline 模式:header+payload 共享总超时,防 Slowloris
message = pmsg_recv(fd, _config.recv_timeout);
} catch (const MsgException&) {
_remove_client(fd);
return;
}
if (_config.workers == 0) {
// 同步模式:在主线程执行回调
try {
_on_message(fd, addr, std::move(message));
} catch (const MsgConnectionException&) {
_remove_client(fd);
} catch (const std::exception& e) {
std::fprintf(stderr, "[MsgServer] callback error on fd=%d: %s\n", fd, e.what());
std::fflush(stderr);
_remove_client(fd);
}
return;
}
// 线程池模式:标记 busy,提交任务
{
std::lock_guard<std::mutex> blk(_busy_mtx);
_busy.insert(fd);
}
{
std::lock_guard<std::mutex> plk(_pool_mtx);
if (static_cast<int>(_pool_queue.size()) >= _config.max_queue_size) {
// 队列已满:拒绝并断开连接(防 OOM)
std::lock_guard<std::mutex> blk(_busy_mtx);
_busy.erase(fd);
_remove_client(fd);
return;
}
_pool_queue.push(Task{fd, std::move(addr), std::move(message)});
}
_pool_cv.notify_one();
}
void _reap_failed() {
std::unordered_set<int> failed;
{
std::lock_guard<std::mutex> flk(_failed_mtx);
failed.swap(_failed);
}
for (int fd : failed) {
{
std::lock_guard<std::mutex> blk(_busy_mtx);
_busy.erase(fd);
}
_remove_client(fd);
}
}
void _remove_client(int fd) {
std::lock_guard<std::mutex> clk(_clients_mtx);
if (_clients.erase(fd) == 0) return; // 已被移除,防止 double-close
_send_locks.erase(fd);
::close(fd);
}
void _close_all_clients() {
std::lock_guard<std::mutex> clk(_clients_mtx);
for (auto& [fd, addr] : _clients) {
::close(fd);
}
_clients.clear();
_send_locks.clear();
std::lock_guard<std::mutex> blk(_busy_mtx);
_busy.clear();
std::lock_guard<std::mutex> flk(_failed_mtx);
_failed.clear();
}
// ---- 成员 --------------------------------------------------------------
std::string _addr;
int _port;
MsgServerConfig _config;
int _server_fd;
std::atomic<bool> _running;
MessageCallback _on_message;
// 客户端管理
mutable std::mutex _clients_mtx;
std::unordered_map<int, std::string> _clients; ///< fd -> "ip:port"
std::unordered_map<int, std::shared_ptr<std::mutex>> _send_locks; ///< fd -> 发送锁
// 同连接串行
std::mutex _busy_mtx;
std::unordered_set<int> _busy; ///< 回调执行中的 fd
// 失败连接(worker 线程标记,主循环清理)
std::mutex _failed_mtx;
std::unordered_set<int> _failed;
// 线程池
std::atomic<bool> _pool_running{false};
std::mutex _pool_mtx;
std::condition_variable _pool_cv;
std::queue<Task> _pool_queue;
};
} // namespace net
} // namespace pmc
+79
View File
@@ -0,0 +1,79 @@
#pragma once
/** libmsg — UniServe 消息通信基础库
*
* 提供 pymsg 协议的 C++ 实现:4字节大端长度前缀 + UTF-8 载荷的 TCP 消息帧协议。
* 对应 Python 版 pymsg/pymsg/__init__.py,字节级兼容。
*
* 命名空间 pmc::net,库文件 libus_msg.so,头文件安装至 /usr/include/us/。
*/
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#define MSG_CHECK(condition, message) \
if (!(condition)) { \
throw std::runtime_error(message); \
}
namespace pmc {
namespace net {
/// 协议常量
namespace msg_constants {
constexpr size_t kHeaderSize = 4; ///< 帧头长度(字节)
constexpr uint32_t kMaxMsgSize = 64u * 1024u * 1024u; ///< 消息最大长度 64 MiB
constexpr int kDefaultTimeout = 10; ///< 默认超时(秒)
constexpr int kSendTimeout = 3; ///< send() 默认超时(秒)
constexpr int kListenPollMs = 100'000; ///< listen() select 轮询间隔(微秒)
constexpr double kRetryLogEvery = 5.0; ///< 重试等待日志间隔(秒)
}
// ============================================================================
// 异常层次
// ============================================================================
/// 消息库基础异常
class MsgException : public std::runtime_error {
public:
explicit MsgException(const std::string &msg) : std::runtime_error(msg) {}
};
/// 消息收发超时
class MsgTimeoutException : public MsgException {
public:
explicit MsgTimeoutException(const std::string &msg) : MsgException(msg) {}
};
/// 连接异常(断开、重置、地址错误等)
class MsgConnectionException : public MsgException {
public:
explicit MsgConnectionException(const std::string &msg) : MsgException(msg) {}
};
/// 目标服务未启动(ECONNREFUSED),用于 retry_on_refused 重试机制
class MsgConnectionRefusedException : public MsgConnectionException {
public:
explicit MsgConnectionRefusedException(const std::string &msg)
: MsgConnectionException(msg) {}
};
// ============================================================================
// 基类(供 MsgClient 等内部工具方法继承使用)
// ============================================================================
class Msg_Basic {
public:
class MsgExp : public std::runtime_error {
public:
MsgExp(const std::string& msg) : std::runtime_error(msg) {}
};
protected:
/// 版本字符串(编译进 .so)
static const char* version();
};
} // namespace net
} // namespace pmc
+181
View File
@@ -0,0 +1,181 @@
#pragma once
/** RpcClient — pymsg 线协议之上的 JSON RPC 客户端(C++ 版,对应 Python 版 pymsg.rpc.RpcClient)
*
* 约定(与 Python/Go 客户端字节级兼容):
* 请求: {"_route": "<路由名>", ...业务字段}
* 响应: {"success": true, "data": ...}
* {"success": false, "error": "..."}
*
* 基于 boost::json 进行 JSON 序列化/反序列化,依赖 MsgClient 进行 TCP 通信。
*
* 注意:此头文件依赖 boost::json,核心 libmsg 库(Msg.hpp/MsgServer.hpp/AsyncQueue.hpp)
* 保持零外部依赖不变。使用 RpcClient 时需要链接 -lboost_json。
*
* Usage:
* #include <us/RpcClient.hpp>
*
* pmc::net::RpcClient rpc("127.0.0.1", 8005, "gateway");
* json::object params;
* params["action"] = "select";
* params["table"] = "Users";
* json::value data = rpc.call("execute", params); // 返回 data 字段
*
* // 启动等待模式(db 未启动时阻塞重试)
* pmc::net::RpcClient rpc("127.0.0.1", 8005, "gateway", 10.0, true);
*/
#include <us/Msg.hpp>
#include <cstddef>
#include <optional>
#include <stdexcept>
#include <string>
#include <boost/json.hpp>
namespace pmc {
namespace net {
namespace json = boost::json;
// ============================================================================
// RpcError — RPC 业务异常
// ============================================================================
/// 远端返回 success=false 的业务错误,或响应不符合 RPC 约定(非 JSON / 缺少 success 字段)。
/// 网络层异常(超时、连接拒绝等)仍抛 MsgTimeoutException / MsgConnectionRefusedException 等。
class RpcError : public MsgException {
public:
explicit RpcError(const std::string& msg) : MsgException(msg) {}
};
// ============================================================================
// RpcClient
// ============================================================================
/// JSON RPC 客户端(短连接,每次调用独立 TCP 连接)。
class RpcClient {
public:
/** 构造 RPC 客户端。
*
* @param addr 服务地址(通常 127.0.0.1)
* @param port 服务端口
* @param name 客户端名称(用于调试/日志)
* @param timeout 默认超时秒数
* @param retry_on_refused 默认是否在 ECONNREFUSED 时阻塞重试
* @param retry_interval 重试间隔秒数
*/
explicit RpcClient(const std::string& addr, int port,
const std::string& name = "rpc",
double timeout = 10.0,
bool retry_on_refused = false,
double retry_interval = 1.0)
: _addr(addr), _port(port), _name(name),
_timeout(timeout),
_retry_on_refused(retry_on_refused),
_retry_interval(retry_interval) {}
// ---- RPC 调用 ----------------------------------------------------------
/** 调用指定路由,返回响应中的 data 字段。
*
* @param route 路由名(自动注入为 _route 字段)
* @param params 业务参数(_route 会被覆盖)
* @param timeout 本次调用超时(nullopt = 使用构造时默认值)
* @param retry_on_refused 本次调用是否重试连接拒绝(nullopt = 使用构造时默认值)
* @return 服务端返回的 data 字段值(可能为 null)
* @throws RpcError 服务端返回 success=false,或响应非法
* @throws MsgTimeoutException 网络超时
* @throws MsgConnectionRefusedException 目标不可达且未重试
* @throws MsgConnectionException 连接中断
*/
json::value call(const std::string& route,
const json::object& params = {},
std::optional<double> timeout = std::nullopt,
std::optional<bool> retry_on_refused = std::nullopt,
std::optional<double> retry_interval = std::nullopt) {
json::object req = params;
req["_route"] = route;
double t = timeout.value_or(_timeout);
bool retry = retry_on_refused.value_or(_retry_on_refused);
double interval = retry_interval.value_or(_retry_interval);
MsgClient client(_addr, _port, _name);
std::string resp_str = client.call(json::serialize(req), t, retry, interval);
json::value resp;
try {
resp = json::parse(resp_str);
} catch (const std::exception& e) {
throw RpcError("响应不是合法 JSON: " + std::string(e.what()));
}
if (!resp.is_object()) {
throw RpcError("非法 RPC 响应(不是 object): " + resp_str.substr(0, 200));
}
const json::object& obj = resp.as_object();
if (!obj.contains("success")) {
throw RpcError("非法 RPC 响应(缺少 success 字段): " + resp_str.substr(0, 200));
}
if (!obj.at("success").as_bool()) {
std::string err = "未知错误";
if (auto it = obj.find("error"); it != obj.end() && it->value().is_string()) {
err = it->value().as_string().c_str();
}
throw RpcError(err);
}
if (auto it = obj.find("data"); it != obj.end()) {
return it->value();
}
return nullptr;
}
/** 调用指定路由,返回完整响应对象(包含 success/data/error),不抛 RpcError。
* 适用于需要自行检查 success 字段或访问额外字段的场景。
*/
json::object call_raw(const std::string& route,
const json::object& params = {},
std::optional<double> timeout = std::nullopt,
std::optional<bool> retry_on_refused = std::nullopt,
std::optional<double> retry_interval = std::nullopt) {
json::object req = params;
req["_route"] = route;
double t = timeout.value_or(_timeout);
bool retry = retry_on_refused.value_or(_retry_on_refused);
double interval = retry_interval.value_or(_retry_interval);
MsgClient client(_addr, _port, _name);
std::string resp_str = client.call(json::serialize(req), t, retry, interval);
json::value resp;
try {
resp = json::parse(resp_str);
} catch (const std::exception& e) {
throw RpcError("响应不是合法 JSON: " + std::string(e.what()));
}
if (!resp.is_object()) {
throw RpcError("非法 RPC 响应(不是 object): " + resp_str.substr(0, 200));
}
return resp.as_object();
}
// ---- 访问器 ------------------------------------------------------------
const std::string& addr() const noexcept { return _addr; }
int port() const noexcept { return _port; }
const std::string& name() const noexcept { return _name; }
private:
std::string _addr;
int _port;
std::string _name;
double _timeout;
bool _retry_on_refused;
double _retry_interval;
};
} // namespace net
} // namespace pmc
+199
View File
@@ -0,0 +1,199 @@
#pragma once
/** RpcServer — pymsg 线协议之上的 JSON RPC 服务端(C++ 版,对应 Python 版 pymsg.rpc.RpcServer)
*
* 约定(与 Python/Go 客户端字节级兼容):
* 请求: {"_route": "<路由名>", ...业务字段}
* 响应: {"success": true, "data": ...}
* {"success": false, "error": "..."}
*
* 基于 MsgServer 提供路由分发、JSON 解析、响应自动包装、异常脱敏等功能。
* handler 签名为 ``json::value(const json::object& req)``:
* - 返回值自动包装为 {"success": true, "data": ...}
* - 抛出的异常自动包装为 {"success": false, "error": "<脱敏后错误信息>"}
* - 服务不会因 handler 异常而崩溃
*
* 注意:此头文件依赖 boost::json,核心 libmsg 库保持零外部依赖不变。
* 使用 RpcServer 时需要链接 -lboost_json。
*
* Usage:
* #include <us/RpcServer.hpp>
*
* pmc::net::RpcServer rpc("127.0.0.1", 8005, {.workers=4});
*
* rpc.add_route("ping", [](const json::object&) -> json::value {
* return "pong";
* });
*
* rpc.add_route("execute", [](const json::object& req) -> json::value {
* // 处理业务逻辑...
* return json::object{{"rows", json::array{}}};
* });
*
* rpc.start(); // 阻塞当前线程
*/
#include <us/MsgServer.hpp>
#include <boost/system/error_code.hpp>
#include <cctype>
#include <cstddef>
#include <functional>
#include <regex>
#include <string>
#include <unordered_map>
#include <boost/json.hpp>
namespace pmc {
namespace net {
namespace json = boost::json;
// ============================================================================
// RpcServer
// ============================================================================
/// JSON RPC 服务端:基于 MsgServer,自带路由分发与响应包装。
class RpcServer {
public:
/// 路由处理函数签名:接收请求参数 object,返回 data 值(任意 JSON 类型)。
using Handler = std::function<json::value(const json::object& req)>;
/// 错误回调:void(route_name, exception),handler 抛异常时调用(用于记日志)。
using ErrorCallback = std::function<void(const std::string& route, const std::exception& e)>;
/** 构造 RPC 服务端。
*
* @param addr 监听地址
* @param port 监听端口
* @param config MsgServer 配置(线程池/超时/连接数限制等)
* @param on_error 可选错误回调,handler 抛异常时被调用
*/
explicit RpcServer(const std::string& addr, int port,
MsgServerConfig config = {},
ErrorCallback on_error = {})
: _server(addr, port, config), _on_error(std::move(on_error)) {}
// ---- 路由注册 ----------------------------------------------------------
/** 注册路由处理函数。
*
* @param name 路由名(对应请求中的 _route 字段)
* @param handler 处理函数,签名 json::value(const json::object& req)
*/
void add_route(const std::string& name, Handler handler) {
_routes[name] = std::move(handler);
}
// ---- 启动 / 停止 -------------------------------------------------------
/// 启动服务(阻塞当前线程)。
void start() {
_server.start([this](int fd, const std::string& addr, const std::string& msg) {
this->_dispatch(fd, addr, msg);
});
}
/// 从另一线程停止服务。
void stop() {
_server.stop();
}
/// 获取底层 MsgServer 引用(可用于 broadcast 等高级操作)。
MsgServer& server() noexcept { return _server; }
private:
// ---- 分发与响应包装 ----------------------------------------------------
void _dispatch(int fd, const std::string& /*addr*/, const std::string& text) {
// 解析 JSON(限制嵌套深度防止栈溢出)
json::parse_options opts;
opts.max_depth = 32; // 与 Python 版一致
boost::system::error_code ec;
json::value req_val = json::parse(text, ec, {}, opts);
if (ec || !req_val.is_object()) {
_reply_error(fd, "invalid JSON request");
return;
}
json::object req = req_val.as_object();
// 提取 _route
std::string route;
if (auto it = req.find("_route"); it != req.end() && it->value().is_string()) {
route = it->value().as_string().c_str();
req.erase(it); // 从业务参数中移除 _route
} else {
_reply_error(fd, "missing _route");
return;
}
// 查找 handler
auto handler_it = _routes.find(route);
if (handler_it == _routes.end()) {
_reply_error(fd, "unknown route: " + route);
return;
}
// 执行 handler
try {
json::value result = handler_it->second(req);
_reply_success(fd, result);
} catch (const std::exception& e) {
if (_on_error) {
try {
_on_error(route, e);
} catch (...) {}
}
// 脱敏后返回:保留业务错误语义,过滤绝对路径等内部细节
try {
_reply_error(fd, _sanitize_error(e.what()));
} catch (...) {
// _sanitize_error 或序列化失败时,使用安全的固定文案
try { _reply_error(fd, "internal error"); } catch (...) {}
}
}
}
void _reply_success(int fd, const json::value& data) {
json::object resp;
resp["success"] = true;
resp["data"] = data;
_server.reply(fd, json::serialize(resp));
}
void _reply_error(int fd, const std::string& error) {
json::object resp;
resp["success"] = false;
resp["error"] = error;
_server.reply(fd, json::serialize(resp));
}
// ---- 错误信息脱敏 ------------------------------------------------------
// 匹配绝对路径(如 /opt/micro_servers/db/xxx.py 或 /usr/lib/...),替换为 <redacted>
// 不返回函数堆栈、SQL、文件路径等内部细节给客户端;完整异常由 on_error 钩子记录到服务端日志。
static std::string _sanitize_error(const std::string& msg) {
if (msg.empty()) return msg;
try {
// 匹配以 / 开头的绝对路径段,替换为 <redacted>
// 不返回函数堆栈、SQL、文件路径等内部细节给客户端
static const std::regex path_re(R"(/[A-Za-z0-9_.\-]+(?:/[A-Za-z0-9_.\-]+)*)");
return std::regex_replace(msg, path_re, "<redacted>");
} catch (...) {
return msg; // 正则异常时返回原文
}
}
// ---- 成员 --------------------------------------------------------------
MsgServer _server;
std::unordered_map<std::string, Handler> _routes;
ErrorCallback _on_error;
};
} // namespace net
} // namespace pmc