engine 消息派发与编解码 C++ API 文档
头文件:
#include <mc/engine.h>/#include <mc/engine/dispatcher.h>命名空间:mc::engine
概述
动态库链接:消息头、消息及 payload 类型通过 MC_API 显式导出,支持调用方在默认隐藏内部符号的构建中跨动态库编解码消息。
mc::engine 的消息派发子系统是 engine 层进程内 / 跨进程通信的核心通道,由三层协作完成:
| 层 | 核心类型 | 职责 |
|---|---|---|
| 入口层 | dispatch(), service::send(), service::send_with_reply(), service::call() |
构造 message 并提交到 engine 内部路由 |
| 消息层 | message, message_header, abstract_payload, make_payload() |
统一消息载体,header 携带路由元数据,body 承载四种标准负载 |
| 编解码层 | encode_message/decode_message 函数组, engine_codec |
message ↔ dict/bytes 序列化/反序列化 |
核心入口 dispatch() 是所有消息发送的统一门面:接收来自 service 的请求消息,通过 engine::route_inbound() 完成内部路由,返回 mc::future<message> 供调用方非阻塞等待响应。service_proto 面向网络协议适配,endpoint_service 面向 SHM 进程间通信。
主要特性
- 明确入口来源:
dispatch()/dispatch_method()保留内部派发语义;D-Bus、SHM、MQ 和 harbor 入口必须调用带 external 后缀的 API,准入判断不依赖目标 executor 类型 - 统一异步入口:
dispatch()将所有消息提交到 engine 路由,返回 future 实现非阻塞响应等待 - 传输无关方法入口:
dispatch_method()供协议适配层直接提交方法调用,复用完整 engine 语义且不引入协议消息类型 - 统一 D-Bus 错误映射:进程内、D-Bus、旧共享内存及 MQ 共用兼容的 D-Bus 错误名,内部诊断正文保留原样;映射和新通道接入规范见 dbus_error_mapping
- 四类标准负载:
method_call、method_return、error、signal,通过payload<>CRTP 基类提供编译期静态类型标识 - 类型安全转换:
try_as<T>()(nullptr on mismatch)和as<T>()(throw on mismatch)双重保障 - 双轨序列化:函数式 API 适用一次性编解码;
engine_codec提供有状态、可复用的编解码器 - Builder 模式:
make_payload<Payload>(args...)一行构造 typed payload - 消息订阅:
add_match/remove_match支持 match_rule + filter_spec 过滤,match_callback 处理匹配消息 - 进程间路由:
endpoint_service在 SHM 模式下按 (endpoint_id, instance_id) 精准投递 - 协议接入:
service_proto提供 inbound_handler + engine_codec 自动编解码链路 - 已接受调用保活:method_call 在对象表锁内取得 target 强引用,跨 executor 投递及实际调用期间持续保活;并发 unregister/stop 只阻止后续查找,不会让已接受调用访问悬垂对象
- 日志归属一致:内联、跨 executor 与目标缺失(信号广播)三条派发路径均安装目标服务短名的日志模块作用域,派发执行期日志归属不随执行路径漂移
- 预算配置可复位:
reset_rpc_config()在应用释放 runtime 所有权时恢复进程级 RPC 默认预算,避免多个 application 依次启动时互相泄漏配置
基本用法
1. 创建 service,发送 method_call 并通过 send_with_reply 同步获取响应
#include <mc/engine.h>
#include <iostream>
#include <cassert>
using namespace mc::engine;
int main()
{
service svc("client.service");
assert(svc.init() && svc.start() && svc.is_healthy());
message req;
req.header.type = message_type::method_call;
req.header.destination = "calculator.service";
req.header.path = "/org/example/calc";
req.header.interface_name = "org.example.Calculator";
req.header.member_name = "Add";
req.header.serial = 1;
req.body = make_payload<method_call_payload>("ii", mc::variants{7, 8});
message reply = svc.send_with_reply(req, mc::milliseconds(3000));
if (reply.header.type == message_type::error) {
auto& err = reply.as<error_payload>();
std::cerr << "RPC error: " << err.name << " — " << err.message << std::endl;
return 1;
}
auto& ret = reply.as<method_return_payload>();
std::cout << "7 + 8 = " << ret.value.as<int>() << std::endl;
svc.stop(); svc.cleanup();
return 0;
}
2. async_send_with_reply 异步调用 + .then() 链式处理
#include <mc/engine.h>
#include <iostream>
using namespace mc::engine;
int main()
{
service svc("async.client");
svc.init(); svc.start();
message req;
req.header.type = message_type::method_call;
req.header.destination = "sensor.service";
req.header.path = "/org/example/sensor";
req.header.interface_name = "org.example.Sensor";
req.header.member_name = "GetTemperature";
req.body = make_payload<method_call_payload>();
svc.async_send_with_reply(req, mc::milliseconds(3000))
.then([](message reply) {
if (auto* ret = reply.try_as<method_return_payload>())
std::cout << "temperature: " << ret->value.as<double>() << std::endl;
else if (auto* err = reply.try_as<error_payload>())
std::cerr << "error: " << err->name << std::endl;
});
svc.stop(); svc.cleanup();
return 0;
}
3. 发射 signal 并通过 add_match 订阅接收
#include <mc/engine.h>
#include <iostream>
#include <cassert>
using namespace mc::engine;
int main()
{
service pub("publisher.service");
service sub("subscriber.service");
pub.init(); pub.start();
sub.init(); sub.start();
int received = 0;
match_rule rule;
rule.type = "signal";
rule.path = "/org/example/sensor/";
rule.interface_name = "org.example.Sensor";
rule.member_name = "TemperatureChanged";
match_id id = sub.add_match(rule, filter_spec{},
[&](const message& msg) {
auto* sig = msg.try_as<signal_payload>();
if (sig && !sig->args.empty()) { ++received; }
});
assert(id != 0);
message sig;
sig.header.type = message_type::signal;
sig.header.path = "/org/example/sensor/temp1";
sig.header.interface_name = "org.example.Sensor";
sig.header.member_name = "TemperatureChanged";
sig.body = make_payload<signal_payload>("d", mc::variants{36.5});
pub.emit(sig);
assert(received == 1);
sub.remove_match(id);
pub.stop(); pub.cleanup();
sub.stop(); sub.cleanup();
return 0;
}
4. endpoint_service 跨进程 SHM 投递
#include <mc/engine.h>
#include <iostream>
#include <cassert>
using namespace mc::engine;
int main()
{
endpoint_service eps("host-bridge");
assert(eps.init() && eps.start());
uint16_t eid = eps.endpoint_id();
uint32_t iid = eps.instance_id();
message req;
req.header.type = message_type::method_call;
req.header.path = "/org/example/remote";
req.header.interface_name = "org.example.Remote";
req.header.member_name = "Ping";
req.body = make_payload<method_call_payload>();
if (eps.send_to_endpoint(2, 1, req))
std::cout << "message queued" << std::endl;
else
std::cerr << "send_to_endpoint failed" << std::endl;
eps.stop(); eps.cleanup();
return 0;
}
API 详细说明
rpc_dispatch_origin
rpc_dispatch_origin::internal 仅用于进程内派发;rpc_dispatch_origin::external 只由
transport 适配器对应的 external API 使用。该枚举是 dispatcher 的内部实现契约,调用方不应
根据 executor 类型推导入口来源。
dispatch — 异步派发入口
MC_API mc::future<message> dispatch(const service& svc, message request);
MC_API mc::future<message> dispatch_external(const service& svc, message request,
mc::runtime::entry_gate* admission_gate);
MC_API mc::future<message> dispatch_external(const service& svc, message request,
mc::runtime::entry_gate* admission_gate,
std::chrono::steady_clock::time_point server_ingress);
| 参数 | 描述 | 返回/异常 |
|---|---|---|
svc |
发送方 service 引用,engine 根据其内部路由表查找目标 | — |
request |
完整的请求消息(含 header + body) | 返回 mc::future<message>,就绪时持有响应消息;std::bad_alloc;路由失败异常通过 future 传播 |
派发先按 path 从 service 对象表取得 mc::shared_ptr<abstract_object>,再读取 target executor 并投递。该强引用会捕获到任务中并传递给派发实现,因此对象在排队后被 unregister 或 service 同时 stop 时,当前调用仍按“查找成功时已接受”的语义完成;拓扑修改不会等待业务调用结束。
外部 method call 找不到目标时,除未注册路径上的 org.freedesktop.DBus.Introspectable.Introspect 外,
会在进入准入队列前立即返回 org.freedesktop.DBus.Error.UnknownObject,不消耗入口额度或队列容量。
未注册路径上的 Introspect 用于中间节点和空 node 探测,仍按外部入口经过准入闸门。
外部 method call 未携带可解码的 method-call payload 时,在进入准入队列前返回
org.freedesktop.DBus.Error.InvalidArgs;闸门关闭时仍返回过载错误。
目标存在但接口或方法未知时,metadata 检查仍在目标执行上下文中完成,以兼容动态对象;请求会快速返回
对应的 UnknownInterface 或 UnknownMethod,但仍受入口有界队列和全局 FIFO 调度约束。
目标已在本 executor 时内联执行:内部 dispatch()/dispatch_method() 若发现目标已在当前
executor,直接在当前线程执行,避免方法体内同步等待自己造成自锁。external 入口即使已经位于
准入 handler 内,也不会因为 TLS 的“已准入”标记而直通;它必须再次通过传输入口提供的闸门。
日志模块名作用域:内联、跨 executor 与目标缺失(信号广播)三条派发路径都会在派发执行期间安装 mc::log::scoped_module_name(值为目标服务短名),作用域内产生的全局日志宏(ilog/wlog/elog 等)输出归属该服务;作用域随派发结束恢复。因此无论调用经过哪条派发路径、落在哪个线程,同一服务的日志归属保持一致(信号广播/目标缺失路径本身无业务 handler,其日志主要来自事件分发与错误回包)。
服务短名语义:作用域取值来自 mc::engine::service_short_name()(<mc/engine/service.h>,取完整服务名最后一个 . 之后的部分,如 bmc.kepler.hwdiscovery → hwdiscovery),返回视图零分配、指向服务名内部存储,末段天然以 \0 结尾可直接作为 C 字符串使用。该实现为引擎通用语义(对任意命名空间适用),与 mc::app::service::to_short_name()(去除固定 bmc.kepler. 前缀)对生产服务名结果一致;mcapp 生命周期(configure/start/stop)与订阅回调的作用域统一使用本实现,保证派发与生命周期两条路径日志归属一致。
异步结果完成:handler 返回的 future 可以绑定其他 executor。dispatcher 使用 immediate_executor 完成内部结果封装,源 future 完成时直接传递值、异常或取消,不再投递到源线程池;因此该池停止后仍能完成响应并释放准入额度。业务自行注册的 continuation 保持其原有调度规则。
dispatch_method 返回的外层 future 使用 immediate_executor 作为完成 executor;目标 executor 负责执行方法。只要 handler 的源 future 最终完成,目标池停止后响应仍可完成,调用方通过 get_executor() 派生的后续 continuation 不会再次依赖目标池存活。
dispatch() 是内部派发入口,不申请入口额度;真实 transport inbound 必须使用
dispatch_external()。admission_gate 是 transport 所属的入口闸门,必须由入口适配器传入,
engine 不从目标对象 executor 推断入口来源。未配置闸门时由适配器选择普通派发路径,关闭中的闸门则返回
mc.engine.rpc_overloaded。目标对象绑定 strand 时,准入任务仍回到 strand 执行,额度到实际
future 完成后才释放。入口任务投递到已停止的目标 executor 时,调度层的丢弃守卫会完成
rpc_overloaded 并释放准入额度,避免 future 永久等待。
外部请求从 gate 移交到目标 executor 后,仍可能继续排队。实际执行入口会统一检查 header/context
计算出的 deadline;标准 D-Bus 接口(包括 Properties.Set)和无对象 Introspect 同样适用。
已过期请求返回 rpc_expired,不启动方法或产生属性写入;普通方法识别 wire context 后还会继续收紧预算。
带 server_ingress 的重载供传输适配器传入消息实际到达服务端的单调时刻,适配器可在自己的
通道排队前盖戳;普通重载使用调用时刻。时间戳作为派发过程的内部 sidecar 传递,不写入
message_header 或 method_dispatch_request,因此不会改变既有公共对象布局。
dispatch_method — 协议适配层方法派发入口
struct method_dispatch_request {
mc::string path;
mc::string interface_name;
mc::string method_name;
mc::string sender;
mc::dict context;
mc::variants args;
};
struct method_dispatch_result {
message_type type;
const mc::reflect::method_type_info* method;
mc::variant value;
mc::string result_signature;
mc::string value_signature;
mc::dict value_signatures;
mc::string error_name;
mc::string error_message;
bool is_method_return() const noexcept;
bool is_error() const noexcept;
};
MC_API mc::future<method_dispatch_result>
dispatch_method(const service& svc, method_dispatch_request request);
MC_API mc::future<method_dispatch_result>
dispatch_method_external(const service& svc, method_dispatch_request request,
mc::runtime::entry_gate* admission_gate);
MC_API mc::future<method_dispatch_result>
dispatch_method_external(const service& svc, method_dispatch_request request,
mc::runtime::entry_gate* admission_gate,
std::chrono::steady_clock::time_point server_ingress);
dispatch_method() 面向内部调用;协议适配层的真实 inbound 应使用 dispatch_method_external()。
两者都不接收 D-Bus、SHM 或其他传输类型;目标查找、标准接口、context 归一化、权限校验、调用事件、
trace、结构化错误、对象 executor 和对象保活语义均共用同一实现。适配层只需把
method_dispatch_result 编码成自己的 wire reply。
in_admitted_entry() 只描述当前已准入执行体的内部派发上下文,不是 external 入口的授权凭据。
external 请求必须每次携带所属 ingress gate,入口闸门关闭或目标 executor 在投递前后停止时均以
rpc_overloaded 完成 future;目标 executor 丢弃已排队闭包的情况也走同一完成契约。
同一 ingress gate 已准入 handler 内再次发起 external 请求会立即返回 rpc_overloaded,避免父请求占用额度并等待同一闸门上的子请求。不同 gate 的请求仍按各自准入策略排队;内部 dispatch_method() 保持当前执行体语义。
#include <mc/engine/dispatcher.h>
#include <mc/engine/service.h>
mc::future<mc::engine::method_dispatch_result>
dispatch_from_transport(mc::engine::service& svc, mc::string path, mc::string interface_name,
mc::string method_name, mc::variants args, mc::runtime::entry_gate* ingress_gate)
{
mc::engine::method_dispatch_request request;
request.path = std::move(path);
request.interface_name = std::move(interface_name);
request.method_name = std::move(method_name);
request.args = std::move(args);
return mc::engine::dispatch_method_external(svc, std::move(request), ingress_gate);
}
传输适配器应传入自己所属的入口闸门;内部对象间调用使用 dispatch_method(),不应把目标
executor 当作外部入口判定依据。
| API | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
dispatch_method |
svc:目标服务;request:传输无关方法请求 |
future<method_dispatch_result> |
std::bad_alloc;异步基础设施异常通过 future 传播 |
可并发调用;具体方法按目标对象 executor 串行语义执行 | 内部派发,不申请入口额度 |
dispatch_method_external |
svc、request、admission_gate:transport 所属入口闸门 |
future<method_dispatch_result> |
std::bad_alloc;关闭闸门返回 rpc_overloaded |
可并发调用;具体方法按目标对象 executor 串行语义执行 | 外部 inbound 派发,准入判断由显式入口来源触发 |
dispatch_method_external(带 ingress) |
同上,加服务端单调接收时刻 | future<method_dispatch_result> |
同上 | 可并发调用 | 传输适配器已在更早阶段盖戳时使用;时间戳不写入 request 布局 |
method_dispatch_result::is_method_return |
无 | 成功结果返回 true |
noexcept |
只读线程安全 | 判断结果是否为 method_return |
method_dispatch_result::is_error |
无 | 错误结果返回 true |
noexcept |
只读线程安全 | 判断结果是否为结构化错误 |
method 指针指向 engine 管理的方法元数据,仅用于适配层立即完成当前结果的签名编码;调用方不应释放或跨对象注销长期缓存该指针。错误通过 error_name/error_message 返回,不需要按错误文本猜测类型。
message 与 message_header
RPC 准入预算配置
MC_API void apply_rpc_config(mc::string_view key, const mc::variant& value) noexcept;
MC_API void reset_rpc_config() noexcept;
| key | 含义 | 默认 |
|---|---|---|
ServerRpcDefaultBudgetMs |
调用方未声明 TimeoutMs 时使用的预算(毫秒,取值 1..86400000) |
60000 |
ServerRpcMaxBudgetMs |
预算上限;客户端声明值只能被收紧,不能放大(毫秒,取值 1..86400000) | 60000 |
默认预算与预算上限独立保存,修改任一项不会改写另一项;两个配置的最终值不受下发顺序影响。
新请求按 min(调用方声明或配置的默认预算, 当前上限) 计算预算:临时调低上限后再调高,
后续请求会重新使用保留的默认预算并受当前上限约束。已确定的排队 deadline 不随配置更新重算。
reset_rpc_config() 恢复上述两个进程级配置为默认值 60000 毫秒;应用释放自己启动的 runtime
时调用,避免多个 application 依次运行时相互继承预算配置。
越界取值(≤0、非数字字符串、bool/浮点、大于 24 小时)一律忽略并记一条 warning,保留原值: 超出一天的预算没有意义,而且逼近「入站时刻 + 预算」的溢出区——溢出到过去会让入口把所有 请求判成过期。未识别的 key 静默忽略(由调用方自己解释)。
engine::dispatch_external() 在入口取得服务端单调接收时刻;传输适配器也可以通过时间戳重载
提前盖戳,目标方法入口据此落定 deadline = server_ingress + budget。该时间戳只在 dispatcher
内部传递,不参与序列化,也不改变 message_header 的公共布局。
第三方调用(busctl 等不声明 TimeoutMs)回退 ServerRpcDefaultBudgetMs,
因此服务端即使拿不到客户端时间也有自己的接收时间基准。预算只用于排队与准入判定,
不作为执行期的硬截止时间。
受理点使用 header context 和 header.timeout 计算排队 deadline。wire 首参中的 TimeoutMs 只有在
目标 executor 结合方法 metadata 完成参数归一化后才能确认;若此时 deadline 已耗尽,方法入口返回
rpc_expired 且不执行用户 handler。这样入口线程不会读取动态对象的懒重建 metadata;wire-only 短预算
请求可能先占用一个额度再在目标 executor 被拒绝。旧框架的 Timeout 是 legacy 键,不消费;畸形、负值
和 0 按未声明处理。
struct message_header {
MC_REFLECTABLE("mc.engine.message_header");
message_type type{message_type::invalid};
mc::string destination, sender, path, interface_name, member_name, error_name;
uint64_t serial{0}, reply_serial{0};
mc::milliseconds timeout{0};
mc::dict context;
};
struct message {
MC_REFLECTABLE("mc.engine.message");
message_header header;
mc::shared_ptr<const abstract_payload> body;
bool empty() const noexcept;
template<typename Payload> const Payload* try_as() const noexcept;
template<typename Payload> const Payload& as() const;
mc::string to_bytes() const;
static message from_bytes(mc::string_view bytes);
};
| 方法 | 描述 | 异常 |
|---|---|---|
empty() |
body 为 nullptr 返回 true | noexcept,不抛出 |
try_as<Payload>() |
类型安全转换:payload_id 匹配则返回 Payload 指针,否则 nullptr | noexcept,不抛出 |
as<Payload>() |
断言转换,类型不匹配时抛出异常 | mc::invalid_arg_exception |
to_bytes() |
序列化为 wire bytes | std::bad_alloc;可能抛 mc::bad_cast_exception |
from_bytes(bytes) |
从 wire bytes 反序列化 | mc::invalid_arg_exception(格式非法) |
Payload 类型与 make_payload
enum class message_type : int32_t {
invalid = 0, method_call = 1, method_return = 2, error = 3, signal = 4,
};
struct method_call_payload : public payload<method_call_payload, 1, message_type::method_call> {
mc::string signature; mc::variants args;
};
struct method_return_payload : public payload<method_return_payload, 2, message_type::method_return> {
mc::variant value; mc::string signature;
};
struct error_payload : public payload<error_payload, 3, message_type::error> {
mc::string name; mc::string message;
};
struct signal_payload : public payload<signal_payload, 4, message_type::signal> {
mc::string signature; mc::variants args;
};
template<typename Payload, typename... Args>
mc::shared_ptr<const abstract_payload> make_payload(Args&&... args);
| 类型 | 描述 | 构造异常 |
|---|---|---|
method_call_payload |
方法调用:signature + variant 参数列表 | std::bad_alloc |
method_return_payload |
方法返回:单个 variant 返回值 | std::bad_alloc |
error_payload |
错误响应:错误名称 + 描述消息 | std::bad_alloc |
signal_payload |
信号广播:signature + variant 参数列表 | std::bad_alloc |
make_payload<Payload>(args...) |
工厂函数:完美转发构造 typed payload | std::bad_alloc;Payload 构造异常向外传播 |
消息编解码
函数式 API
MC_API mc::dict encode_payload(const abstract_payload& payload);
MC_API mc::shared_ptr<const abstract_payload> decode_payload(const mc::dict& dict);
MC_API mc::dict encode_message(const message& msg);
MC_API message decode_message(const mc::dict& dict);
MC_API mc::string encode_message_bytes(const message& msg);
MC_API message decode_message_bytes(mc::string_view bytes);
| 函数 | 描述 | 异常 |
|---|---|---|
encode_payload(p) |
payload → dict | std::bad_alloc |
decode_payload(d) |
dict → payload(按 payload_id 分派类型) | mc::invalid_arg_exception |
encode_message(m) |
header + body → dict | std::bad_alloc |
decode_message(d) |
dict → header + body | mc::invalid_arg_exception(字段缺失或类型错误) |
encode_message_bytes(m) |
message → dict → bytes 全链编码 | std::bad_alloc;mc::bad_cast_exception |
decode_message_bytes(b) |
bytes → dict → message | mc::invalid_arg_exception(格式非法) |
engine_codec
class engine_codec {
public:
engine_codec() = default;
explicit engine_codec(message_decode_options opt) noexcept;
mc::string encode(const message& msg) const;
message decode(mc::string_view bytes) const;
void set_decode_options(message_decode_options opt) noexcept;
};
| 方法 | 描述 | 异常 |
|---|---|---|
engine_codec() |
默认构造 | noexcept |
engine_codec(opt) |
带 options 构造 | noexcept |
encode(msg) |
message → wire bytes | std::bad_alloc;mc::bad_cast_exception |
decode(bytes) |
wire bytes → message(应用 options) | mc::invalid_arg_exception |
set_decode_options(opt) |
替换 options,后续 decode 立即生效 | noexcept |
service — 服务端核心
class service : public mc::object {
public:
explicit service(mc::string_view name, mc::object* parent = nullptr);
mc::string_view name() const;
bool init(dict args = {}); bool start(); bool stop();
void cleanup(); bool is_healthy() const;
void register_object(abstract_object& obj);
void unregister_object(mc::string_view path);
void unregister_private_object(abstract_object& obj);
void reindex_object(abstract_object& obj, const std::function<void()>& mutate);
void check_object_name_available(const abstract_object& obj, mc::string_view new_name) const;
void emit(const message& msg) const;
void send(message request) const;
message send_with_reply(message request, mc::milliseconds timeout = 5000) const;
mc::future<message> async_send_with_reply(message request, mc::milliseconds timeout = 5000) const;
mc::variant call(mc::string_view path, mc::string_view service_name,
mc::string_view interface, mc::string_view method,
const mc::variants& args = {}, mc::string_view sig = {},
mc::milliseconds timeout = 5000) const;
match_id add_match(match_rule rule, filter_spec spec, match_callback callback) const;
void remove_match(match_id id) const;
};
| 方法 | 描述 | 异常 |
|---|---|---|
service(name) |
构造,挂载 parent 对象树 | std::bad_alloc |
init(args) |
初始化,调 on_init;成功返回 true | on_init 异常向外传播;失败返回 false |
start() |
启动,调 on_start | on_start 异常向外传播 |
stop() |
停止,调 on_stop | on_stop 异常向外传播 |
cleanup() |
清理资源,调 on_cleanup | hook 异常被 catch 记日志,不向外传播 |
is_healthy() |
健康状态查询 | 不抛出 |
register_object(obj) |
入服务表(空 path→私有表)并镜像全局表;入表后同步 is_registered=true,再异步 on_register |
路径非法 / 索引冲突等 |
unregister_object(path) |
先摘表再清 is_registered,再异步 on_unregister |
不抛出(未找到为空操作) |
unregister_private_object(obj) |
私有对象:先摘表再清 registered | 不抛出 |
reindex_object(obj, mutate) |
服务表+全局表嵌套 table::reindex;未绑定本服务时仅 mutate |
唯一冲突等;须先 check_object_name_available |
check_object_name_available(obj, name) |
跨服务/私有/全局表预检 object_name | 冲突抛 invalid_arg_exception |
emit(msg) |
发射信号;先调 backend::emit,返回 true 则走 engine emit | backend 异常向外传播 |
send(request) |
fire-and-forget 异步发送 | 路由异常记日志,不向外传播 |
send_with_reply(req, timeout) |
同步阻塞等待响应 | 超时抛 mc::timeout_exception |
async_send_with_reply(req, timeout) |
异步发送,返回 future | std::bad_alloc;错误通过 future 传播 |
call(path, service, iface, method, args, sig, timeout) |
一站式 RPC:构造→发送→解析 | 超时 mc::timeout_exception;远端 error 抛 mc::method_call_exception |
add_match(rule, spec, cb) |
注册匹配规则 | std::bad_alloc |
remove_match(id) |
按 match_id 移除 | 不抛出(id 不存在静默忽略) |
service_proto — 协议适配层
class service_proto : public mc::proto::protocol {
public:
void set_inbound_handler(std::function<mc::future<message>(message)> handler);
void clear_inbound_handler();
bool has_inbound_handler() const noexcept;
void set_decode_options(message_decode_options opt);
const engine_codec& codec() const noexcept;
virtual mc::future<message> async_send_with_reply(message req, mc::milliseconds timeout = 5000);
virtual message send_with_reply(message req, mc::milliseconds timeout = 5000);
void send(message req);
};
| 方法 | 描述 | 异常 |
|---|---|---|
set_inbound_handler(h) |
设置入站处理器 | std::bad_alloc |
clear_inbound_handler() |
清除入站处理器 | 不抛出 |
codec() |
获取内部 engine_codec 引用 | noexcept |
set_decode_options(opt) |
设置内部 codec decode 选项 | noexcept |
send_with_reply(req, timeout) |
同步传输扩展点;默认等待 async_send_with_reply,协议实现可直接完成同步请求 |
超时 mc::timeout_exception |
async_send_with_reply(req, timeout) |
异步发送 | std::bad_alloc;错误通过 future 传播 |
send(req) |
发送无应答消息 | 底层 proto 异常向外传播 |
service_backend 后端事件钩子:
emit(msg)(返回 false 拦截分发)、on_object_added(obj)、on_object_removed(obj)、on_match_added(id, rule)、on_match_removed(id)。所有虚方法覆盖异常均向外传播。
endpoint_service — SHM 进程端点
SHM 模式下每进程有且仅有一个 endpoint_service。on_start 中完成 endpoint 注册、mq_channel 打开与绑定。
class endpoint_service : public service {
public:
endpoint_service(mc::string_view endpoint_name, mc::object* parent = nullptr);
std::uint16_t endpoint_id() const noexcept;
std::uint32_t instance_id() const noexcept;
bool send_to_endpoint(uint16_t eid, uint32_t iid, const message& msg);
mc::future<message> send_with_reply_to_endpoint(uint16_t eid, uint32_t iid,
message msg, mc::milliseconds timeout);
};
| 方法 | 描述 | 异常 |
|---|---|---|
endpoint_id() |
SHM endpoint 编号 | noexcept |
instance_id() |
SHM 实例编号 | noexcept |
send_to_endpoint(eid, iid, msg) |
向指定 endpoint 投递消息,成功返回 true | std::bad_alloc;写失败返回 false |
send_with_reply_to_endpoint(eid, iid, msg, timeout) |
发送并等待应答 | 超时 mc::timeout_exception |
engine 静态方法
class engine {
public:
static void route_inbound(const message& msg);
static void publish(const message& msg);
static void register_service(service* svc);
static void unregister_service(service* svc);
static service* find_service(mc::string_view name);
static bool is_service_registered(const service* svc);
static endpoint_service* get_endpoint_service() noexcept;
};
| 方法 | 描述 | 异常 |
|---|---|---|
route_inbound(msg) |
将入站消息路由到目标 service 或匹配订阅 | 路由异常记日志 |
publish(msg) |
向所有 match 订阅者广播消息 | match_callback 异常向外传播 |
register_service(svc) |
向 engine 注册 service | std::bad_alloc |
unregister_service(svc) |
从 engine 注销 service | 不抛出 |
find_service(name) |
按名称查找 service,未找到返回 nullptr | 不抛出 |
is_service_registered(svc) |
按指针校验 service 是否仍在本进程注册表中(延迟清理回调用) | 不抛出 |
get_endpoint_service() |
获取当前进程的 endpoint_service,无则返回 nullptr | noexcept |
最佳实践
✅ 使用 make_payload + try_as/as 模式安全访问 payload
try_as<T>() 返回 nullptr on mismatch,适合分支派发;as<T>() 在类型已确认后使用,避免重复检查:
#include <mc/engine.h>
#include <iostream>
using namespace mc::engine;
void handle_response(const message& reply)
{
if (auto* err = reply.try_as<error_payload>()) {
std::cerr << "RPC error: " << err->name << " — " << err->message << std::endl;
return;
}
auto& ret = reply.as<method_return_payload>();
std::cout << "result: " << ret.value.as<int>() << std::endl;
}
✅ 消息 header.type 必须与 body payload 类型严格一致
header.type 是路由和序列化的第一级判别依据。类型不匹配时 try_as 全部返回 nullptr,接收方无法解析:
#include <mc/engine.h>
using namespace mc::engine;
void consistent_message()
{
message sig;
sig.header.type = message_type::signal;
sig.body = make_payload<signal_payload>("d", mc::variants{36.5}); // ✅
message req;
req.header.type = message_type::method_call;
req.header.interface_name = "org.example.Calc";
req.header.member_name = "Add";
req.body = make_payload<method_call_payload>("ii", mc::variants{7, 8}); // ✅
}
❌ 未在 service 析构前移除 match 订阅导致悬空回调
add_match 将回调注册到全局 match table,service 析构后若未调用 remove_match,match table 仍持有指向已析构对象的回调引用:
#include <mc/engine.h>
using namespace mc::engine;
// ❌ 错误:service 析构后 match table 持有悬空 this 回调
struct BadService : public service {
BadService(mc::string_view n) : service(n) {}
void on_init(dict) override {
m_id = add_match(match_rule{}, {}, [this](const message&) { do_work(); });
}
void do_work() {}
match_id m_id{0};
};
// ✅ 正确:在 stop 钩子中主动取消订阅
struct GoodService : public service {
GoodService(mc::string_view n) : service(n) {}
void on_init(dict) override {
m_id = add_match(match_rule{}, {}, [this](const message&) { do_work(); });
}
bool on_stop() override {
if (m_id != 0) { remove_match(m_id); m_id = 0; }
return true;
}
void do_work() {}
match_id m_id{0};
};
❌ 不要在 dispatch 返回的 future 就绪前销毁 service
dispatch() 持有 service 的内部路由信息,future 未就绪时 service 析构会导致 engine 访问悬空引用:
#include <mc/engine.h>
#include <memory>
using namespace mc::engine;
void bad_early_destroy()
{
auto svc = std::make_unique<service>("temp.service");
svc->init(); svc->start();
message req;
req.header.type = message_type::method_call;
req.header.destination = "target.service";
req.body = make_payload<method_call_payload>();
mc::future<message> fut = dispatch(*svc, req);
svc.reset(); // ❌ future 可能尚未就绪,engine 持有指向已析构 svc 的引用
}
void correct_lifetime()
{
service svc("stable.service");
svc.init(); svc.start();
message req;
req.header.type = message_type::method_call;
req.header.destination = "target.service";
req.body = make_payload<method_call_payload>();
mc::future<message> fut = dispatch(svc, req);
message reply = fut.get(); // ✅ 阻塞等待,确保 svc 在 future 就绪前存活
if (auto* ret = reply.try_as<method_return_payload>())
std::cout << "ok" << std::endl;
svc.stop(); svc.cleanup();
}