1. DDS发布订阅基础通信

DDS发布订阅基础通信 学习卡片

任务目标

用 DDS 建立一个最小可运行的发布/订阅链路:发布者周期发布车载传感器数据(车速、转速),订阅者接收并打印。本案例使用 Eclipse Cyclone DDS 的 C API,单进程内同时启动发布者与订阅者,便于直接验证。

环境与依赖

  • Linux(或 WSL),已安装 CMake ≥ 3.10 与 GCC。
  • 安装 Cyclone DDS:sudo apt install cyclonedds-dev(或从源码构建后 source install/setup.sh)。
  • 确认头文件与库存在:pkg-config --cflags --libs cyclonedds 能输出编译参数。

数据模型与通信结构

传感器数据用固定结构体 SensorData 表示,包含时间戳、车速、转速。发布者写入 VehicleSensor 主题,订阅者监听同一主题。DDS 通过主题名 + 类型名匹配,因此两端必须使用完全一致的类型定义与主题名。

发布者 Publisher 写 SensorData 主题 Topic VehicleSensor 类型 SensorData 订阅者 Subscriber 读 SensorData DDS 按 主题名 + 类型名 自动匹配,无需指定对端地址

核心代码

单文件 dds_demo.c:先创建参与者、主题、发布者与订阅者,订阅者用监听器回调接收数据,主线程每 500ms 发布一次,共 5 次后退出。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "dds/dds.h"

typedef struct {
    int64_t timestamp;
    float   speed;
    float   rpm;
} SensorData;

static volatile int g_received = 0;

/* 订阅者回调:每收到一条数据打印一次 */
static void on_data(dds_entity_t reader, void *arg)
{
    (void)arg;
    void *samples[1] = {0};
    dds_sample_info_t infos[1];
    while (dds_take(reader, samples, infos, 1, 1) > 0) {
        if (infos[0].valid_data) {
            SensorData *s = (SensorData *)samples[0];
            printf("[SUB] t=%lld speed=%.1f rpm=%.0f\n",
                   (long long)s->timestamp, s->speed, s->rpm);
            g_received++;
        }
        dds_return_loan(reader, samples, 1);
    }
}

int main(void)
{
    dds_entity_t participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL);
    if (participant < 0) { fprintf(stderr, "create participant failed\n"); return 1; }

    dds_entity_t topic = dds_create_topic(participant, &SensorData_desc, "VehicleSensor", NULL, NULL);
    if (topic < 0) { fprintf(stderr, "create topic failed\n"); return 1; }

    dds_entity_t writer = dds_create_writer(participant, topic, NULL, NULL);
    dds_entity_t reader = dds_create_reader(participant, topic, NULL, NULL);
    if (writer < 0 || reader < 0) { fprintf(stderr, "create writer/reader failed\n"); return 1; }

    dds_listener_t *listener = dds_create_listener(NULL);
    dds_lset_data_available(listener, on_data);
    dds_set_listener(reader, listener);

    for (int i = 0; i < 5; i++) {
        SensorData s = { .timestamp = (int64_t)time(NULL),
                         .speed = 40.0f + i * 5.0f,
                         .rpm   = 1500.0f + i * 200.0f };
        dds_write(writer, &s);
        printf("[PUB] sent speed=%.1f rpm=%.0f\n", s.speed, s.rpm);
        usleep(500000);
    }

    sleep(1); /* 等待订阅者处理完最后一条 */
    printf("total received = %d\n", g_received);

    dds_delete_listener(listener);
    dds_delete(participant);
    return 0;
}

编译与运行

gcc dds_demo.c -o dds_demo $(pkg-config --cflags --libs cyclonedds)
./dds_demo

验证方法与通过条件

  • 运行后终端应交替出现 [PUB] sent ... 与 [SUB] t=... speed=... rpm=... 行。
  • 发布 5 条后,最后一行 total received = 5,说明订阅者完整收到全部数据。
  • 若只看到 [PUB] 而无 [SUB],检查主题名与类型是否一致、DDS 域是否相同(默认域 0)。

常见问题

  • 链接报未定义符号:确认 pkg-config 能找到 cyclonedds,或手动加 -lddsc。
  • 回调不触发:dds_take 需在回调内循环读取,且必须调用 dds_return_loan 归还样本。
  • 多进程验证:把发布与订阅拆成两个进程运行,主题名与类型保持一致即可互通。