https://ekxide.github.io/iceoryx2-book/main/getting-started/robot-nervous-system/blackboard.html#
黑板通信模式总结
应用场景:
- 需要在分布式系统中集中管理大量配置参数(如传感器频率、电压阈值等)
- 避免pub-sub模式下"1个发布者 + N个订阅者"导致的内存冗余问题
- 实现配置的动态更新和全局共享
核心优势:
- 内存高效:所有节点共享同一份配置内存,避免数据拷贝
- 类型安全:编译时和运行时类型检查
- 跨语言支持:使用兼容的数据类型(如StaticString或整数作为键)
关键约束:
- 必须由唯一一个"创建者"节点初始化所有键和默认值
- 其他节点只能以"打开者"身份访问已存在的黑板服务
C++实现代码(带详细注释)
1. 写入端(配置管理节点)
cpp
#include "iceoryx2.hpp"
#include "iox2/container/static_string.hpp"
using namespace iox2;
int main() {
// 1. 创建节点
auto node = NodeBuilder().create<ServiceType::Ipc>().value();
// 2. 定义配置键(使用跨语言兼容的StaticString)
using KeyType = container::StaticString<50>;
// 电池阈值配置键
auto battery_key = container::StaticString<50>::from_utf8("battery_threshold");
// 超声波传感器更新频率配置键
auto us_sensor_key = container::StaticString<50>::from_utf8("ultra_sonic_sensor_update_rate_in_ms");
// 检查键创建是否成功
if (!battery_key.has_value() || !us_sensor_key.has_value()) {
std::cerr << "Blackboard keys could not be created." << std::endl;
return 1;
}
// 3. 创建黑板服务并初始化默认值
// .blackboard_creator<KeyType>() - 声明为创建者角色
// .add<T>(key, default_value) - 添加键值对
auto service = node.service_builder(ServiceName::create("global_config").value())
.blackboard_creator<KeyType>()
// 电池阈值默认为25%
.template add<float>(battery_key.value(), 0.25)
// 传感器更新频率默认为100ms
.template add<uint32_t>(us_sensor_key.value(), 100)
.create()
.value();
// 4. 创建写入端口
auto writer = service.writer_builder().create().value();
// 5. 获取各配置项的句柄(类型安全)
auto battery_threshold_handle = writer.template entry<float>(battery_key.value()).value();
auto update_rate_handle = writer.template entry<uint32_t>(us_sensor_key.value()).value();
// 6. 主循环:定期检查并更新配置
while (node.wait(iox2::bb::Duration::from_millis(100)).has_value()) {
// 获取新的电池阈值(示例函数)
auto new_battery_threshold = get_battery_threshold();
if (new_battery_threshold.has_value()) {
// 更新配置值(浅拷贝)
battery_threshold_handle.update_with_copy(new_battery_threshold.value());
}
// 获取新的传感器频率(示例函数)
auto new_update_rate = get_update_rate();
if (new_update_rate.has_value()) {
update_rate_handle.update_with_copy(new_update_rate.value());
}
}
return 0;
} 2. 读取端(配置使用节点)
cpp
#include "iceoryx2.hpp"
#include "iox2/container/static_string.hpp"
using namespace iox2;
int main() {
// 1. 创建节点
auto node = NodeBuilder().create<ServiceType::Ipc>().value();
// 2. 定义键类型(必须与写入端一致)
using KeyType = container::StaticString<50>;
// 3. 打开已存在的黑板服务
// .blackboard_opener<KeyType>() - 声明为打开者角色
auto service = node.service_builder(ServiceName::create("global_config").value())
.blackboard_opener<KeyType>()
.open()
.value();
// 4. 创建读取端口
auto reader = service.reader_builder().create().value();
// 5. 获取传感器更新频率的句柄
auto us_sensor_key = container::StaticString<50>::from_utf8("ultra_sonic_sensor_update_rate_in_ms").value();
auto update_rate_handle = reader.template entry<uint32_t>(us_sensor_key).value();
// 6. 创建普通发布者(用于发送传感器数据)
auto publisher_service = node.service_builder(ServiceName::create("ultrasonic_sensor").value())
.publisher<Distance>()
.create()
.value();
auto publisher = publisher_service.writer_builder().create().value();
// 7. 主循环:使用黑板中的配置频率进行数据发布
while (true) {
// 从黑板获取当前的更新频率
uint32_t current_update_rate = *update_rate_handle.get();
// 按配置的频率等待
if (!node.wait(iox2::bb::Duration::from_millis(current_update_rate)).has_value()) {
break;
}
// 采集并发布传感器数据
auto sample = publisher.loan_uninit().value();
auto initialized_sample = sample.write_payload(
Distance { get_ultra_sonic_sensor_distance(), 42.0 }
);
send(std::move(initialized_sample)).value();
}
return 0;
} 关键设计模式总结:
创建者-打开者模式:
- 一个创建者初始化所有配置
- 多个打开者读取/更新配置
类型安全访问:
entry<T>()确保类型匹配- 错误的类型会导致编译或运行时错误
内存共享机制:
- 所有节点访问同一内存位置
- 更新立即对所有读取者可见
适用数据类型:
- 平凡可拷贝类型
- 共享内存兼容类型
- 跨语言时使用整数或StaticString作为键
性能对比:
- 传统pub-sub:1MB配置 × 1000节点 = 1001MB预留内存
- 黑板模式:1MB配置 + 少量元数据 ≈ 1MB实际使用内存
相关示例:
- Rust/Python/C++/C语言版本可在iceoryx2示例仓库中找到