Files
blackbird/libmsg/README.md
qing 1d44e0dcb8 feat(libmsg): add pymsg C++ library with AsyncQueue, MsgServer, and JSON RPC layer
- Add libmsg module: pymsg protocol implementation (4-byte BE length prefix + UTF-8)
- Add AsyncQueue<T>: generic async batch queue with backpressure (aligned with Python AsyncRpcQueue)
- Add MsgServer: select-based multi-client TCP server with optional thread pool
- Add RpcClient/RpcServer: JSON RPC convention layer with automatic route injection
- Refactor gateway/LogC.hpp to use AsyncQueue for async batch log delivery
- Update gateway/DbC.hpp to add call_msg() using pymsg TCP protocol
- Remove duplicated gateway/inc/Msg.hpp (now provided by libmsg)
- Update gateway/Makefile and _inst.sh build order

Fixes included:
- pmsg_send size check uses kMaxMsgSize (64MiB) instead of 4GiB
- MsgClient._running uses std::atomic<bool> for thread safety
- Deadline-based recv timeout to prevent Slowloris (header+payload shared deadline)
2026-08-06 23:48:06 +08:00

546 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# libmsg — UniServe 消息通信库
C++20 轻量级 TCP 消息帧通信库,实现 pymsg 协议(4字节大端长度前缀 + UTF-8载荷),与 Python 版 pymsg 字节级兼容。提供底层帧收发函数、MsgClient 客户端、MsgServer 多客户端服务端,以及通用异步批量队列 AsyncQueue。
命名空间 `pmc::net`,库文件 `libus_msg.so`,头文件安装至 `/usr/include/us/`。被 `gateway`、`db`、`dnfs`、`smtp`、`captcha`、`confs` 等所有使用 pymsg 协议的 C++ 服务依赖。
## 架构
```
pmc::net::Msg_Basic (基类: 版本信息 / 异常层次 / MSG_CHECK 宏)
└── MsgClient TCP 客户端(短连接 send/call + 长连接 listen)
pmc::net::MsgServer TCP 服务端(select 事件循环 + 可选线程池 + reply/broadcast)
pmc::net::AsyncQueue<T> 通用异步批量发送队列(对应 Python AsyncRpcQueue)
pmc::net::RpcClient JSON RPC 客户端(_route 注入 + success/error 自动解包)—— 需 boost::json
pmc::net::RpcServer JSON RPC 服务端(路由分发 + 响应包装 + 异常脱敏)—— 需 boost::json
pmc::net 命名空间下的自由函数:
├── pmsg_send(fd, data) 底层协议发送
├── pmsg_recv(fd) 底层协议接收(阻塞模式,依赖 SO_RCVTIMEO)
└── pmsg_recv(fd, timeout_sec) 底层协议接收(deadline 模式,总超时精确控制)
pmc::net::msg_constants 协议常量(kHeaderSize=4, kMaxMsgSize=64MiB 等)
异常层次:
├── MsgException 基础异常
│ ├── MsgTimeoutException 收发超时
│ ├── MsgConnectionException 连接异常
│ │ └── MsgConnectionRefusedException ECONNREFUSED(用于重试)
│ └── RpcError JSON RPC 业务错误(success=false 或响应非法)
```
所有收发函数均为 header-only(inline 实现),`AsyncQueue<T>` 和 `MsgServer` 也是 header-only,无第三方依赖(仅 POSIX socket + 标准库线程)。`version()` 编译进 `libus_msg.so`。
`RpcClient` 和 `RpcServer` 是可选的 JSON RPC 约定层,基于 `boost::json` 提供路由分发和响应自动包装,对应 Python 版 `pymsg.rpc`。使用时需额外链接 `-lboost_json`,核心库不引入此依赖。
## 线协议
```
+--------+---------------------------+
| 4 B BE | N B UTF-8 |
| length | payload |
+--------+---------------------------+
```
- 长度字段:32 位无符号整数,大端序(network byte order)
- 载荷:UTF-8 编码文本(通常为 JSON)
- 最大消息长度:64 MiB(防 OOM 攻击)
- 传输层:TCP,无 TLS(信任本机网络边界,默认绑定 127.0.0.1)
## 类与 API
### Msg_Basic — 基类
提供版本查询和异常类型,`MsgClient` 继承此类。
| 成员 | 说明 |
|------|------|
| `version()` (static protected) | 返回版本字符串(编译进 .so) |
| `MsgExp` | 异常类,继承 `std::runtime_error` |
| `MSG_CHECK(cond, msg)` | 宏,条件为 false 时抛出 `std::runtime_error` |
### 协议常量(`msg_constants` 命名空间)
| 常量 | 值 | 说明 |
|------|----|------|
| `kHeaderSize` | 4 | 帧头长度(字节) |
| `kMaxMsgSize` | 64 MiB | 消息最大长度(防 OOM) |
| `kDefaultTimeout` | 10 秒 | call()/connect() 默认超时 |
| `kSendTimeout` | 3 秒 | send() 默认超时 |
| `kListenPollMs` | 100000 微秒 | listen() select 轮询间隔 |
| `kRetryLogEvery` | 5.0 秒 | 重试等待日志打印间隔 |
### 底层协议函数
```cpp
// 发送一条消息(长度前缀 + payload)
void pmsg_send(int sockfd, const std::string& data);
// 接收一条消息(阻塞模式,依赖 socket 已设置的 SO_RCVTIMEO)
// 抛出 MsgConnectionException(连接断开)或 MsgTimeoutException(超时)
std::string pmsg_recv(int sockfd);
// 接收一条消息(deadline 模式,总超时不超过 timeout_sec)
// header+payload 共享同一个 deadline,使用 select() 实现精确超时控制
// 适用于客户端 call() 和服务端,防止总等待时间翻倍
std::string pmsg_recv(int sockfd, double timeout_sec);
```
### MsgClient — TCP 客户端
```cpp
#include <us/Msg.hpp>
// 构造:指定服务地址、端口、客户端名称(用于 send() 前缀)
pmc::net::MsgClient client("127.0.0.1", 8005, "gateway");
```
| 方法 | 说明 |
|------|------|
| `connect()` | 创建长连接(供 listen() 使用) |
| `send(msg)` | 短连接发送 ``"name: msg"`` 格式消息,不等待回复 |
| `call(msg, timeout=10, retry_on_refused=false, retry_interval=1.0)` | 短连接 RPC:发送原始消息,阻塞等待回复 |
| `listen(on_message)` | 长连接持续接收,回调签名 `void(const std::string&)` |
| `stop()` | 设置停止标志并关闭套接字,可跨线程调用 |
| `addr()` / `port()` / `name()` | 访问器 |
**call() 的 retry_on_refused 机制**:
设为 `true` 时,仅对 `ECONNREFUSED`(目标服务未启动)以 1 秒间隔阻塞重试,每 5 秒打印一次等待日志。适用于微服务启动顺序不确定的场景:服务先启动的一方会自动等待依赖方就绪,无需额外服务发现组件。其他异常(超时、连接重置等)仍立即抛出。
### MsgServer — TCP 服务端
```cpp
#include <us/MsgServer.hpp>
// 构造:绑定地址和端口,可配置线程池大小
pmc::net::MsgServer server("0.0.0.0", 8003, {.workers=4});
```
对应 Python 版 `pymsg.MsgServer`,提供基于 select 的多客户端 TCP 服务端。
**配置结构**:
```cpp
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 轮询超时(秒),影响关闭响应速度
};
```
| 方法 | 说明 |
|------|------|
| `start(on_message)` | 启动服务(阻塞当前线程);回调签名 `void(int fd, const string& addr, const string& msg)` |
| `stop()` | 停止服务(可从任意线程调用,包括回调内部) |
| `reply(fd, msg)` | 向指定客户端回复消息(线程安全,经每连接锁串行化) |
| `broadcast(msg, except_fd=-1)` | 向所有客户端广播(线程安全),可排除指定 fd |
| `client_count()` | 当前已连接客户端数量 |
**设计特性**:
- **两种并发模式**:`workers=0` 同步模式(简单高效),`workers>0` 线程池并发模式
- **同连接串行保证**:同一连接的消息严格串行处理(回调执行期间暂停 select 监听该 fd)
- **线程安全发送**:`reply()`/`broadcast()` 经每连接 mutex 串行化,防止帧交错
- **连接数限制**:`max_connections` 防止连接耗尽攻击
- **有界任务队列**:`max_queue_size` 防止慢 handler 导致 OOM,队列满时断开连接
- **Slowloris 防护**:使用 deadline 模式 recv(select + 剩余时间),总接收时间不超过 `recv_timeout`
- **优雅关闭**:`stop()` 可从任意线程调用,事件循环退出前清理所有连接
- **故障隔离**:worker 线程异常仅标记 failed fd,主循环统一清理,避免 fd 并发操作
- **TCP_NODELAY**:新连接自动禁用 Nagle 算法,降低小包延迟
### AsyncQueue\<T\> — 通用异步批量发送队列
```cpp
#include <us/AsyncQueue.hpp>
```
对应 Python 版 `pymsg.AsyncRpcQueue`,提供内存队列 + 后台线程的异步批量处理机制,适用于日志上报、指标采集、事件通知等非关键路径场景。业务线程 `push()` 非阻塞立即返回,后台线程攒批处理(批量阈值或超时),失败时通过错误回调通知,绝不阻塞主业务流程。
**配置结构**:
```cpp
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; // 后台线程轮询超时(秒)
};
```
| 方法 | 说明 |
|------|------|
| `AsyncQueue(handler, config={}, on_error={})` | 构造队列;`handler` 签名 `void(vector<T>&& batch)`,`on_error` 签名 `void(const exception&, vector<T>&&)` |
| `start()` | 启动后台发送线程(幂等) |
| `push(item)` | 非阻塞入队;返回 false 表示队列已关闭 |
| `flush()` | 在调用线程同步 flush 当前队列中的所有数据,阻塞直到处理完成 |
| `shutdown()` | 优雅关闭:flush 剩余数据后停止线程(幂等,析构自动调用) |
| `pending()` | 当前队列中待处理的数据量 |
| `dropped()` | 因队列满而丢弃的数据总数 |
| `is_running()` | 是否已 start 且未 shutdown |
**设计特性**:
- **非阻塞入队**:`push()` 立即返回,不等待网络 I/O
- **批量发送**:攒到 `batch_size` 条或超过 `flush_interval` 秒自动 flush
- **背压保护**:`max_queue_size` 限制内存队列长度,满时丢最旧数据并递增 `dropped()` 计数
- **优雅关闭**:`shutdown()` flush 所有剩余数据后停止后台线程
- **RAII**:析构自动调用 `shutdown()`
- **可插拔**:Handler 回调可自定义(RPC 调用、写文件、发消息等)
- **fail-safe**:Handler 抛异常时触发 `on_error` 回调,绝不传播到业务线程
### RpcClient — JSON RPC 客户端
```cpp
#include <us/RpcClient.hpp> // 需链接 -lboost_json
// 构造:指定服务地址、端口、客户端名称
pmc::net::RpcClient rpc("127.0.0.1", 8005, "gateway");
// 启动等待模式(db 未启动时阻塞重试)
pmc::net::RpcClient rpc("127.0.0.1", 8005, "gateway", 10.0, true);
```
对应 Python 版 `pymsg.rpc.RpcClient`,在 `MsgClient` 之上封装 JSON RPC 约定:
- 自动注入 `_route` 字段
- 自动解析响应 `{"success":true,"data":...}` / `{"success":false,"error":"..."}`
- 业务错误抛 `RpcError`,网络错误仍抛原有网络异常
| 方法 | 说明 |
|------|------|
| `RpcClient(addr, port, name, timeout=10, retry_on_refused=false, retry_interval=1.0)` | 构造 RPC 客户端 |
| `call(route, params={}, timeout=nullopt, retry=nullopt, retry_interval=nullopt)` | 调用路由,返回 `data` 字段值;业务错误抛 `RpcError` |
| `call_raw(route, params={}, timeout=nullopt, retry=nullopt, retry_interval=nullopt)` | 调用路由,返回完整响应 `object`(含 success/data/error),不抛 `RpcError` |
| `addr()` / `port()` / `name()` | 访问器 |
**RPC 约定**:
- 请求格式:`{"_route": "<路由名>", ...业务字段}`
- 成功响应:`{"success": true, "data": ...}`
- 错误响应:`{"success": false, "error": "<错误信息>"}`
- 非 JSON 响应或缺 success 字段抛 `RpcError("响应不是合法 JSON: ...")`
### RpcServer — JSON RPC 服务端
```cpp
#include <us/RpcServer.hpp> // 需链接 -lboost_json
// 构造:绑定地址和端口,可透传 MsgServerConfig
pmc::net::RpcServer rpc("127.0.0.1", 8005, {.workers=4});
// 注册路由
rpc.add_route("ping", [](const json::object&) -> json::value {
return "pong";
});
rpc.start(); // 阻塞当前线程
```
对应 Python 版 `pymsg.rpc.RpcServer`,在 `MsgServer` 之上封装路由分发和响应自动包装。
| 方法 | 说明 |
|------|------|
| `RpcServer(addr, port, config={}, on_error={})` | 构造 RPC 服务端;`on_error` 签名 `void(const string& route, const exception& e)` |
| `add_route(name, handler)` | 注册路由;`handler` 签名 `json::value(const json::object& req)` |
| `start()` | 启动服务(阻塞当前线程) |
| `stop()` | 从另一线程停止服务 |
| `server()` | 获取底层 `MsgServer&` 引用(可用于 broadcast 等) |
**Handler 约定**:
- 返回值自动包装为 `{"success": true, "data": <返回值>}`
- 抛出 `std::exception` 自动包装为 `{"success": false, "error": "<脱敏后错误>"}`
- 返回 `nullptr` 表示无数据(客户端收到 `data=null`)
- 业务参数中 `_route` 字段已被移除,不会透传给 handler
**安全特性**:
- JSON 解析限制嵌套深度 32 层,防止栈溢出攻击
- 错误信息自动脱敏:绝对路径替换为 `<redacted>`,不泄露内部文件路径
- `on_error` 钩子可记录完整异常到服务端日志(脱敏仅对客户端响应生效)
- Handler 异常不导致服务崩溃
### 异常层次
| 异常类 | 含义 | 来源 |
|--------|------|------|
| `MsgException` | 所有消息库异常的基类 | — |
| `MsgTimeoutException` | send/recv 超时 | SO_RCVTIMEO/SO_SNDTIMEO 或 deadline 到期触发 |
| `MsgConnectionException` | 连接断开/重置/地址错误 | recv 返回0/错误、connect 失败 |
| `MsgConnectionRefusedException` | connect 返回 ECONNREFUSED | 仅用于 retry_on_refused 判定 |
| `RpcError` | JSON RPC 业务错误 | 远端返回 success=false、响应非 JSON、缺少 success 字段 |
## 构建与安装
**前置依赖**:g++(支持 C++20)、POSIX 系统(Linux/macOS),无第三方库依赖。
```bash
# 一键构建 + 安装(需 sudo)
bash _set.sh
# 或手动
make # 编译 libus_msg.so
make install # 安装头文件到 /usr/include/us/ + .so 到 /lib/
make clear # 清理构建产物
make uninstall # 卸载
```
使用:
```cpp
#include <us/Msg_Basic.hpp>
#include <us/Msg.hpp>
#include <us/MsgServer.hpp> // 如需服务端
#include <us/AsyncQueue.hpp> // 如需异步队列
#include <us/RpcClient.hpp> // 如需 JSON RPC 客户端(需 boost::json)
#include <us/RpcServer.hpp> // 如需 JSON RPC 服务端(需 boost::json)
```
链接:
```bash
# 仅使用核心库(Msg/MsgServer/AsyncQueue)
g++ -std=c++20 ... -lus_msg
# 使用 JSON RPC 层(RpcClient/RpcServer)
g++ -std=c++20 ... -lus_msg -lboost_json
```
> 与 `libus_crypt` 不同,`libus_msg.so` 不依赖 OpenSSL,无需额外链接 `-lssl -lcrypto`。所有通信函数、MsgServer 和 AsyncQueue 均为 header-only 内联实现,链接 `-lus_msg` 仅引入版本符号。
## 使用示例
### RPC 请求-应答(短连接)
```cpp
#include <us/Msg.hpp>
#include <iostream>
int main() {
pmc::net::MsgClient client("127.0.0.1", 8005, "demo");
// 简单调用(不重试)
try {
std::string resp = client.call(R"({"_route":"execute","sql":"SELECT 1"})", 5.0);
std::cout << "Response: " << resp << std::endl;
} catch (const pmc::net::MsgConnectionRefusedException& e) {
std::cerr << "服务未启动: " << e.what() << std::endl;
}
// 启动等待模式(阻塞直到 db 就绪)
std::string resp = client.call(
R"({"_route":"execute","sql":"SELECT 1"})",
5.0,
/*retry_on_refused=*/true
);
std::cout << "Response after wait: " << resp << std::endl;
return 0;
}
```
### 发送即忘(短连接通知)
```cpp
pmc::net::MsgClient log_client("127.0.0.1", 8005, "log");
log_client.send("application started"); // 发送 "log: application started"
```
### 长连接监听
```cpp
#include <us/Msg.hpp>
#include <iostream>
int main() {
pmc::net::MsgClient listener("127.0.0.1", 9000, "subscriber");
listener.connect();
listener.listen([](const std::string& msg) {
std::cout << "Received: " << msg << std::endl;
});
return 0;
}
```
### Echo 服务端(同步模式)
```cpp
#include <us/MsgServer.hpp>
#include <iostream>
#include <thread>
int main() {
pmc::net::MsgServer server("127.0.0.1", 8003);
std::thread t([&] {
server.start([&](int fd, const std::string& addr, const std::string& msg) {
server.reply(fd, "echo: " + msg);
});
});
// ... 服务运行 ...
// 另一线程调用 server.stop() 可优雅关闭
server.stop();
t.join();
return 0;
}
```
### 广播聊天服务端(线程池模式)
```cpp
#include <us/MsgServer.hpp>
#include <thread>
int main() {
pmc::net::MsgServer server("0.0.0.0", 8003, {
.workers = 4,
.recv_timeout = 10.0,
.max_connections = 1000,
.max_queue_size = 1000
});
server.start([&](int fd, const std::string& addr, const std::string& msg) {
// 转发给所有人(排除发送者自己)
server.broadcast("[" + addr + "] " + msg, /*except_fd=*/fd);
});
return 0;
}
```
### 异步批量队列(日志上报)
```cpp
#include <us/AsyncQueue.hpp>
#include <us/Msg.hpp>
#include <string>
#include <vector>
#include <iostream>
struct LogEntry {
std::string service;
std::string message;
};
int main() {
pmc::net::AsyncQueue<LogEntry> queue(
[](std::vector<LogEntry>&& batch) {
pmc::net::MsgClient client("127.0.0.1", 8005, "metrics");
for (auto& e : batch) {
// 发送到日志服务...
}
},
{50, 2.0, 10000, 0.5},
[](const std::exception& e, std::vector<LogEntry>&&) {
std::cerr << "[log] send failed: " << e.what() << std::endl;
}
);
queue.start();
queue.push(LogEntry{"gateway", "request handled"});
queue.push(LogEntry{"gateway", "db connected"});
// ... 应用运行 ...
queue.shutdown();
return 0;
}
```
### JSON RPC 服务端 + 客户端
```cpp
#include <us/RpcServer.hpp>
#include <us/RpcClient.hpp>
#include <thread>
#include <iostream>
int main() {
// 服务端
pmc::net::RpcServer rpc("127.0.0.1", 8005, {.workers=4},
[](const std::string& route, const std::exception& e) {
std::cerr << "[rpc][" << route << "] error: " << e.what() << std::endl;
});
rpc.add_route("ping", [](const json::object&) -> json::value {
return "pong";
});
rpc.add_route("add", [](const json::object& req) -> json::value {
return req.at("a").to_number<int>() + req.at("b").to_number<int>();
});
std::thread t([&]{ rpc.start(); });
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 客户端
pmc::net::RpcClient client("127.0.0.1", 8005, "demo");
json::object params;
params["a"] = 3;
params["b"] = 4;
json::value result = client.call("add", params);
std::cout << "3 + 4 = " << result.as_int64() << std::endl; // 7
try {
client.call("nonexistent");
} catch (const pmc::net::RpcError& e) {
std::cout << "error: " << e.what() << std::endl; // unknown route: nonexistent
}
rpc.stop();
t.join();
return 0;
}
```
## 项目结构
```
libmsg/
├── inc/us/
│ ├── Msg_Basic.hpp # 基类:异常层次、常量、版本声明
│ ├── Msg.hpp # 核心协议:pmsg_send/pmsg_recv + MsgClient(header-only)
│ ├── MsgServer.hpp # TCP 服务端:多客户端 select 事件循环 + 线程池(header-only)
│ ├── AsyncQueue.hpp # 通用异步批量队列(header-only 模板)
│ ├── RpcClient.hpp # JSON RPC 客户端(header-only,需 boost::json)
│ └── RpcServer.hpp # JSON RPC 服务端(header-only,需 boost::json)
├── src/
│ └── Msg_Basic.cpp # version() 实现
├── Makefile
├── _set.sh
└── README.md
```
## 注意事项
- **无外部依赖**:libmsg 核心(Msg/MsgServer/AsyncQueue)仅使用 POSIX socket API(socket/connect/recv/send/select)和 C++ 标准库(thread/mutex/condition_variable/deque/functional/unordered_map),不依赖 OpenSSL、Boost 或任何第三方库。
- **JSON RPC 层依赖**:`RpcClient`/`RpcServer` 是基于 `boost::json` 的可选约定层,使用时需安装 Boost 并链接 `-lboost_json`。核心 `libus_msg.so` 不依赖 Boost。
- **线程安全**:
- `MsgClient` 的 `stop()` 方法使用 `std::atomic` 保护标志位和 fd,可跨线程安全调用;`connect()` 会安全关闭旧连接。同一 `MsgClient` 实例的 `call()`/`send()`/`listen()` 不应并发调用(每个线程应创建独立实例)。`listen()` 中使用局部 fd 副本防止 stop() 关闭后 FD_SET(-1) 导致未定义行为。
- `MsgServer` 的 `start()` 阻塞调用线程;`stop()`/`reply()`/`broadcast()`/`client_count()` 可在任意线程(包括消息回调内部)安全调用。消息回调在同步模式下运行于主线程,在线程池模式下运行于 worker 线程。reply()/broadcast() 经每连接 mutex 串行化防止帧交错。
- `AsyncQueue<T>` 的 `push()`/`flush()`/`shutdown()`/`pending()`/`dropped()` 可在任意线程安全调用;Handler 回调在后台线程中执行。`push()` 使用 mutex + condition_variable 唤醒后台线程,无锁竞争热点。
- `RpcClient` 每次 `call()` 创建独立短连接,实例可在多线程中安全使用(但建议每个线程独立实例以避免连接复用问题)。
- `RpcServer` 的 `add_route()` 应在 `start()` 之前调用;`stop()`/`server()` 可跨线程安全调用。Handler 在 MsgServer 回调线程中执行(同步模式=主线程,线程池模式=worker线程)。
- **macOS 兼容**:自动处理 `MSG_NOSIGNAL` 缺失问题(macOS 内核不会在 send 到已关闭连接时发 SIGPIPE)。
- **超时行为**:
- `MsgClient::call()` 使用 deadline 模式 recv(select + 剩余时间递减),header+payload 共享同一个总超时,不会出现"header 等 t 秒 + payload 再等 t 秒"的双倍超时问题。
- `MsgClient::listen()` 使用 SO_RCVTIMEO 做 100ms 轮询超时,结合 `_running` 原子标志实现快速停止响应。
- `MsgServer` 使用 deadline 模式(select + 剩余时间递减),单条消息总接收时间不超过 `recv_timeout`,有效防御 Slowloris 攻击。
- `AsyncQueue::flush()` 在调用线程同步执行 `_send_batch()`,阻塞直到 handler 处理完成;`shutdown()` 永久等待后台线程结束(handler 中 socket 均有超时,不会永久阻塞)。
- **消息大小限制**:收发两端均检查 64 MiB 上限(`kMaxMsgSize`),防止超大消息导致 OOM。
- **retry_on_refused 仅对 ECONNREFUSED 重试**:连接超时、连接重置、路由不可达等错误不重试,立即抛出异常。
- **MsgServer 同步 vs 线程池**:
- `workers=0`(默认):回调在主线程执行,适合 CPU 轻量、无阻塞 I/O 的场景(如简单 echo、内存状态查询)。同步模式下回调异常会被捕获、记日志并断开该客户端,不会导致服务崩溃。
- `workers>0`:回调提交到线程池,适合有 I/O 阻塞的场景;同一连接消息仍严格串行,不同连接可并行处理。`stop()` 会等待所有已排队任务处理完成(优雅关闭),不丢弃请求。
- **AsyncQueue 背压策略**:队列满时丢最旧数据(drop-oldest),而非阻塞 push 或丢最新数据,保证最新数据不丢失同时防止内存无限增长。`batch_size` 是触发阈值而非批量上限,flush 时取走队列中所有数据一次处理。
- **跨语言兼容**:C++ 实现与 Python 版 `pymsg`、Go 版(waf 内)字节级完全兼容,可无缝互操作。
## 许可证
MIT License