Match/Routing 系统 API 文档
Header:
#include <mc/engine.h>Namespace:mc::engine::match
概述
mc::engine::match 提供消息订阅与路由分发的核心基础设施。系统基于 match_rule(六字段匹配规则)进行消息筛选,支持 path_pattern(路径模式匹配)和 filter_backend(可扩展过滤后端),通过 table(订阅表)统一管理订阅生命周期,并与 mc::engine::engine 深度集成。
典型流程:service 通过 add_match() 注册匹配规则(规则进 table,回调进 callback_registry)→ 引擎在 route_inbound()/publish() 时调用 table::find_targets() 查找目标订阅 → 派发执行点经 callback_registry::lookup(match_id) 解析并触发 match_callback。回调在执行点解析(而非派发入队时绑定),remove_match 后已排队但未执行的回调会被跳过(弱保证语义)。
主要特性
- 六字段匹配规则:
match_rule包含 type、sender、destination、path、interface_name、member_name;空字符串自动视为通配,匹配任意值 - 路径模式匹配:
path_pattern支持全通配(空串"")、精确匹配("/foo/bar")、前缀双星号("/foo/")、单段通配("*")和任意子路径("**") - 可扩展过滤系统:
filter_backend抽象接口通过filter_backend_registry单例注册,支持编译→缓存→评估管线;内置condition_filter_backend(类型标识condition_filter = 1) - 统一回调解析:
callback_registry进程级单例维护match_id → match_callback映射,table 只管理匹配规则;三种 table 形态(local/shared/旧 SHM 后端)共用同一回调存储 - 日志归属:
service::add_match()包装的订阅回调在订阅服务 executor 上执行时自动安装订阅服务短名的日志模块作用域,回调内全局日志宏归属订阅方 - 端点存活清理:
table::sweep_dead_endpoint()定期回收已断开端点的所有订阅,防止资源泄漏 - 引擎级集成:
engine提供静态方法get_match_table()/set_match_table()/add_filter_backend()管理全局路由表和过滤后端 - service 便捷接口:
add_match()/remove_match()将订阅生命周期绑定到 service;on_match_added/on_match_removed信号暴露订阅变更事件
基本用法
示例 1:table 订阅与回调触发
#include <mc/engine.h>
#include <iostream>
#include <cassert>
int main()
{
using namespace mc::engine;
auto table = engine::get_match_table();
match_rule rule;
rule.type = "signal";
rule.path = "/org/example/sensors/";
// table 只登记匹配规则;回调绑定到 callback_registry
match_id id = table->subscribe("my_service", rule, filter_spec{});
assert(id != 0);
assert(table->size() == 1);
match::callback_registry::instance().bind(id, [](const message& msg) {
std::cout << "收到信号: path=" << msg.header.path
<< " member=" << msg.header.member_name << std::endl;
});
message msg;
msg.header.type = message_type::signal;
msg.header.path = "/org/example/sensors/temperature";
msg.header.member_name = "PropertiesChanged";
auto targets = table->find_targets(msg);
assert(!targets.empty());
// 执行点解析:经 callback_registry 按 match_id 查找回调
match_callback cb = match::callback_registry::instance().lookup(targets[0].id);
assert(cb);
cb(msg); // 输出: 收到信号: path=/org/example/sensors/temperature member=PropertiesChanged
match::callback_registry::instance().unbind(id);
table->unsubscribe(id);
assert(table->size() == 0);
return 0;
}
示例 2:match_rule 与 path_pattern 匹配模式
#include <mc/engine.h>
#include <iostream>
#include <cassert>
int main()
{
using namespace mc::engine::match;
// full_wildcard: 空字符串匹配任意路径
path_pattern p_empty("");
assert(p_empty.get_kind() == path_pattern::kind::full_wildcard);
assert(p_empty.matches("/any/path"));
assert(p_empty.matches("/deeply/nested/path/here"));
// exact: 不含通配符,仅精确匹配
path_pattern p_exact("/org/example/sensor");
assert(p_exact.get_kind() == path_pattern::kind::exact);
assert(p_exact.matches("/org/example/sensor"));
assert(!p_exact.matches("/org/example/sensor/temp"));
// prefix_doublestar: 末尾 '/' 表示前缀匹配所有子路径
path_pattern p_prefix("/org/example/");
assert(p_prefix.get_kind() == path_pattern::kind::prefix_doublestar);
assert(p_prefix.matches("/org/example/sensor"));
assert(p_prefix.matches("/org/example/sensor/temp/value"));
assert(!p_prefix.matches("/other/path"));
// 单段通配 '*'
path_pattern p_seg("/org/*/status");
assert(p_seg.is_well_formed("/org/*/status"));
assert(p_seg.matches("/org/example/status"));
assert(!p_seg.matches("/org/example/sub/status"));
// 复合:match_rule + matches 自由函数
match_rule rule;
rule.type = "signal";
rule.path = "/com/example/";
mc::engine::message_header hdr;
hdr.type = mc::engine::message_type::signal;
hdr.path = "/com/example/device/status";
assert(matches(rule, hdr));
hdr.type = mc::engine::message_type::method_call;
assert(!matches(rule, hdr));
return 0;
}
示例 3:自定义 filter_backend 注册与评估
#include <mc/engine.h>
#include <iostream>
#include <cassert>
constexpr uint32_t kPathLenFilter = 100;
class path_len_compiled : public mc::engine::filter_compiled {
public:
explicit path_len_compiled(int min_len) : m_min_len(min_len) {}
int min_len() const { return m_min_len; }
private:
int m_min_len;
};
class path_len_backend : public mc::engine::filter_backend {
public:
uint32_t backend_type() const noexcept override { return kPathLenFilter; }
mc::string_view name() const noexcept override { return "path_len"; }
mc::engine::filter_compiled_ptr compile(mc::string_view text) const override {
int min_len = std::stoi(std::string(text));
return mc::make_shared<path_len_compiled>(min_len);
}
bool evaluate(const mc::engine::filter_compiled& compiled,
const mc::engine::message& msg) const override {
auto& c = dynamic_cast<const path_len_compiled&>(compiled);
return static_cast<int>(msg.header.path.size()) > c.min_len();
}
};
int main()
{
using namespace mc::engine;
auto backend = mc::make_shared<path_len_backend>();
filter_backend_registry::instance().register_backend(backend);
auto found = filter_backend_registry::instance().find(kPathLenFilter);
assert(found != nullptr);
assert(found->name() == "path_len");
auto compiled = filter_backend_registry::instance()
.get_or_compile(kPathLenFilter, "10");
assert(compiled != nullptr);
message msg;
msg.header.path = "/org/example/sensors/temperature"; // 长度 > 10
assert(filter_backend_registry::instance()
.evaluate(kPathLenFilter, "10", msg));
msg.header.path = "/ab"; // 长度 ≤ 10
assert(!filter_backend_registry::instance()
.evaluate(kPathLenFilter, "10", msg));
filter_backend_registry::instance().clear_cache();
assert(filter_backend_registry::instance().cache_size() == 0);
return 0;
}
示例 4:find_targets 多服务分发
#include <mc/engine.h>
#include <iostream>
#include <cassert>
int main()
{
using namespace mc::engine;
auto table = engine::get_match_table();
size_t base = table->size();
match_rule rule;
rule.type = "signal";
rule.path = "/com/example/events/";
match_id id_a = table->subscribe("svc_a", rule, filter_spec{});
match_id id_b = table->subscribe("svc_b", rule, filter_spec{});
assert(table->size() == base + 2);
match_rule rule2;
rule2.type = "signal";
rule2.path = "/other/path/";
match_id id_c = table->subscribe("svc_c", rule2, filter_spec{});
message msg;
msg.header.type = message_type::signal;
msg.header.path = "/com/example/events/temperature";
auto targets = table->find_targets(msg);
assert(targets.size() == 2); // 仅 svc_a 和 svc_b 命中
for (const auto& t : targets) {
std::cout << "target: service=" << t.service_name
<< " endpoint=" << t.endpoint_id
<< " match_id=" << t.id << std::endl;
}
table->unsubscribe(id_a);
table->unsubscribe(id_b);
table->unsubscribe(id_c);
assert(table->size() == base);
return 0;
}
API 详细说明
match_rule 结构体
六字段匹配规则。空字符串字段自动视为通配,匹配任意值。path 字段额外支持 path_pattern 语法。
| 字段 | 类型 | 描述 |
|---|---|---|
type |
mc::string |
消息类型字符串,如 "signal"、"method_call",空串匹配任意类型 |
sender |
mc::string |
发送者过滤,空串匹配任意发送者 |
destination |
mc::string |
目标过滤,空串匹配任意目标 |
path |
mc::string |
路径过滤,支持 path_pattern 语法(前缀 /、*、**) |
interface_name |
mc::string |
D-Bus 接口名过滤 |
member_name |
mc::string |
方法/信号/属性名过滤 |
| 自由函数 | 描述 | 异常 |
|---|---|---|
bool matches(const match_rule& rule, const message_header& header) noexcept |
检查消息头是否匹配规则,path 字段使用 path_pattern::matches |
不抛出 |
path_pattern 类
match_rule::path 的模式表达式。支持空串全通配、精确路径、前缀双星号、单段通配 *、任意子路径 **。非法语法容错降级为 full_wildcard。
| 方法 | 描述 | 异常 |
|---|---|---|
static bool is_well_formed(mc::string_view raw) noexcept |
检查路径字符串格式是否合法 | 不抛出 |
path_pattern() |
默认构造,kind 为 full_wildcard | 不抛出 |
explicit path_pattern(mc::string_view raw) |
解析路径模式,非法语法降级为 full_wildcard | std::bad_alloc(segments 分配失败) |
kind get_kind() const noexcept |
返回模式类型 | 不抛出 |
bool matches(mc::string_view path) const noexcept |
检查给定路径是否匹配当前模式 | 不抛出 |
const std::vector<mc::string>& segments() const noexcept |
返回解析后的路径段列表 | 不抛出 |
kind 枚举 |
典型输入 | 匹配行为 |
|---|---|---|
full_wildcard |
"" |
匹配任意路径 |
exact |
"/foo/bar" |
仅精确匹配 /foo/bar |
prefix_doublestar |
"/foo/" |
匹配 /foo 及其所有子路径 |
filter_spec 结构体
| 字段 | 类型 | 描述 |
|---|---|---|
backend_type |
uint32_t |
过滤后端类型标识,默认 no_filter(=0) 表示不过滤 |
text |
mc::string |
过滤表达式文本,由对应 backend 解释 |
| 方法 | 描述 | 异常 |
|---|---|---|
bool empty() const noexcept |
backend_type == no_filter 时返回 true |
不抛出 |
filter_backend 与 filter_compiled
filter_backend 是可扩展的过滤后端基类。compile() 返回 filter_compiled_ptr(mc::shared_ptr<filter_compiled>),编译后对象可跨消息复用。
| 方法 | 描述 | 异常 |
|---|---|---|
virtual uint32_t backend_type() const noexcept = 0 |
返回全局唯一的后端类型标识 | 不抛出 |
virtual mc::string_view name() const noexcept = 0 |
返回后端可读名称 | 不抛出 |
virtual filter_compiled_ptr compile(mc::string_view text) const = 0 |
编译 filter_spec::text 为编译后对象 |
实现定义的异常向上传播 |
virtual bool evaluate(const filter_compiled& compiled, const message& msg) const = 0 |
使用编译后对象评估消息是否通过过滤 | 实现定义的异常向上传播 |
filter_backend_registry 类(单例)
全局过滤后端注册中心和编译缓存。
| 方法 | 描述 | 异常 |
|---|---|---|
static filter_backend_registry& instance() |
获取单例引用 | 不抛出 |
void register_backend(filter_backend_ptr backend) |
注册过滤后端,同类型重复注册会覆盖 | std::bad_alloc |
filter_backend_ptr find(uint32_t backend_type) const |
按类型查找后端,未找到返回 nullptr |
不抛出 |
filter_compiled_ptr get_or_compile(uint32_t backend_type, mc::string_view text) |
查找缓存或编译过滤表达式,结果自动缓存 | 后端 compile 异常向上传播 |
bool evaluate(uint32_t backend_type, mc::string_view text, const message& msg) |
查找后端并评估消息是否通过过滤 | 后端 compile/evaluate 异常向上传播 |
void set_cache_capacity(std::size_t capacity) |
设置编译缓存最大容量 | 不抛出 |
std::size_t cache_capacity() const noexcept |
返回缓存最大容量 | 不抛出 |
std::size_t cache_size() const noexcept |
返回当前已缓存条目数 | 不抛出 |
void clear_cache() |
清空编译缓存 | 不抛出 |
table 抽象类
订阅与匹配的统一入口。全局单例由 engine 持有,通过 engine::get_match_table() 获取。
| 方法 | 描述 | 异常 |
|---|---|---|
virtual match_id subscribe(mc::string_view service_name, match_rule rule, filter_spec spec) = 0 |
登记匹配规则,返回唯一 match_id(非零);回调需另行 callback_registry::bind |
std::bad_alloc |
virtual void unsubscribe(match_id id) noexcept = 0 |
按 id 取消订阅,id 不存在时静默忽略 | 不抛出 |
virtual void clear() noexcept = 0 |
清空所有订阅 | 不抛出 |
virtual std::size_t size() const noexcept = 0 |
返回当前订阅总数 | 不抛出 |
virtual std::vector<target> find_targets(const message& msg) const = 0 |
查找消息匹配的所有目标订阅 | std::bad_alloc |
virtual std::size_t sweep_dead_endpoint(uint16_t endpoint_id, uint32_t min_alive_instance_id) noexcept |
清理已断开端点的所有订阅,返回清理数量 | 不抛出 |
callback_registry 类(单例)
Header: #include <mc/engine/match/callback_registry.h>
进程级 match_id → match_callback 映射。回调与匹配规则分离存储:table 只管规则与 match_id 分配,回调在派发执行点经 registry 解析。unbind 后已排队但未执行的派发会因解析失败而跳过(remove_match 弱保证语义的实现基础)。
| 方法 | 描述 | 异常 |
|---|---|---|
static callback_registry& instance() |
获取进程级单例引用 | 不抛出 |
void bind(match_id id, match_callback callback) |
绑定回调,同 id 重复绑定覆盖;id 为 0 或回调为空时静默忽略 | std::bad_alloc |
void unbind(match_id id) noexcept |
解绑回调,id 不存在时静默忽略 | 不抛出 |
match_callback lookup(match_id id) const |
按 id 查找回调,未找到返回空 std::function |
不抛出 |
std::size_t size() const noexcept |
返回当前绑定数量 | 不抛出 |
void clear() noexcept |
清空所有绑定(engine reset 时调用) | 不抛出 |
target 结构体
find_targets 返回的匹配目标。关联类型:match_id = std::uint64_t(0 视为未注册),match_callback = std::function<void(const message&)>。
| 字段 | 类型 | 描述 |
|---|---|---|
endpoint_id |
uint16_t |
目标端点 ID |
instance_id |
uint32_t |
目标实例 ID |
service_name |
mc::string |
订阅服务名 |
id |
match_id |
订阅句柄 |
engine 静态方法(match 相关)
| 方法 | 描述 | 异常 |
|---|---|---|
static match::table_ptr get_match_table() |
获取全局 match table | 不抛出 |
static void set_match_table(match::table_ptr table) |
替换全局 match table,常用于测试注入 mock | 不抛出 |
static void add_filter_backend(match::filter_backend_ptr backend) |
将过滤后端注册到全局 filter_backend_registry |
std::bad_alloc |
static match::filter_backend_ptr find_filter_backend(uint32_t backend_type) |
按类型查找已注册的过滤后端 | 不抛出 |
service 订阅方法
mc::engine::service 提供的便捷接口,内部委托给全局 match table。
| 方法 | 描述 | 异常 |
|---|---|---|
match_id add_match(match_rule rule, filter_spec spec, match_callback callback) const |
以当前 service 身份订阅 | std::bad_alloc |
void remove_match(match_id id) const |
取消指定订阅,id 不存在时静默忽略 | 不抛出 |
| 信号 | 签名 | 描述 |
|---|---|---|
on_match_added |
mc::signal<void(match_id, const match_rule&)> |
订阅添加时发射 |
on_match_removed |
mc::signal<void(match_id)> |
订阅移除时发射 |
最佳实践
✅ 使用 path 前缀匹配替代全通配缩小范围
当只关心特定子树的消息时,使用前缀模式(末尾 /)而非空串全通配,避免不相关消息触发回调:
// 好:仅匹配 /org/example/sensors/ 子树下的信号
mc::engine::match_rule rule;
rule.type = "signal";
rule.path = "/org/example/sensors/";
service->add_match(rule, {}, callback);
// 差:空 path 匹配全路径树的信号,回调频次不可控
rule.path = "";
✅ match_rule 粗筛 + filter_spec 细滤组合使用
match_rule 的字段比对是轻量级 O(1) 操作,适合第一级粗筛;filter_spec 在 match_rule 通过后执行,适合条件表达式等细粒度过滤:
mc::engine::match_rule rule;
rule.type = "signal";
rule.path = "/org/example/devices/";
rule.interface_name = "org.example.Device"; // 粗筛:限定接口
mc::engine::filter_spec spec;
spec.backend_type = mc::engine::condition_filter;
spec.text = "severity >= 3"; // 细滤:仅处理高严重度信号
auto id = service->add_match(rule, spec, [](const mc::engine::message& msg) {
// 仅 severity >= 3 的 Device 接口信号到达此处
});
❌ 未在 service 析构前取消订阅导致悬空回调
service::add_match 将回调注册到全局 match table。若 service 析构前未调用 remove_match,match table 仍持有指向已析构对象的回调引用:
// 差:service 析构后 match table 持有悬空回调
struct BadService : public mc::engine::service {
void init() {
mc::engine::match_rule rule; rule.type = "signal";
m_id = add_match(rule, {}, [this](const mc::engine::message&) {
process_event(); // this 可能已析构
});
}
void process_event() {}
mc::engine::match_id m_id{0};
};
// 好:在停止逻辑中主动取消订阅
struct GoodService : public mc::engine::service {
void init() {
mc::engine::match_rule rule; rule.type = "signal";
m_id = add_match(rule, {}, [this](const mc::engine::message&) {
process_event();
});
}
void stop() { if (m_id != 0) { remove_match(m_id); m_id = 0; } }
void process_event() {}
mc::engine::match_id m_id{0};
};
❌ 在 filter_backend::evaluate 中执行 I/O 或持锁操作
evaluate 在消息分发热路径上被调用,可能对每个候选订阅执行一次。应确保 evaluate 仅为纯计算,无 I/O、无锁竞争:
// 差:evaluate 中执行阻塞 I/O
bool evaluate(const mc::engine::filter_compiled& compiled,
const mc::engine::message& msg) const override {
auto remote_state = fetch_remote_config(); // 每次 evaluate 都执行
return remote_state.matches(msg.header.path);
}
// 好:将外部状态固化到 compiled 对象,compile 阶段完成 I/O
mc::engine::filter_compiled_ptr compile(mc::string_view text) const override {
auto remote_state = fetch_remote_config(); // 仅编译时执行一次
return mc::make_shared<my_compiled>(std::move(remote_state));
}
bool evaluate(const mc::engine::filter_compiled& compiled,
const mc::engine::message& msg) const override {
auto& c = dynamic_cast<const my_compiled&>(compiled);
return c.remote_state.matches(msg.header.path); // 纯内存比较
}