Skip to content

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;
}

关键设计模式总结

  1. 创建者-打开者模式

    • 一个创建者初始化所有配置
    • 多个打开者读取/更新配置
  2. 类型安全访问

    • entry<T>()确保类型匹配
    • 错误的类型会导致编译或运行时错误
  3. 内存共享机制

    • 所有节点访问同一内存位置
    • 更新立即对所有读取者可见
  4. 适用数据类型

    • 平凡可拷贝类型
    • 共享内存兼容类型
    • 跨语言时使用整数或StaticString作为键

性能对比

  • 传统pub-sub:1MB配置 × 1000节点 = 1001MB预留内存
  • 黑板模式:1MB配置 + 少量元数据 ≈ 1MB实际使用内存

相关示例

  • Rust/Python/C++/C语言版本可在iceoryx2示例仓库中找到

基于 VitePress 构建