4. 嵌套结构体消息

任务目标

在FastDDS中定义包含嵌套结构体的自定义消息类型,并基于该类型完成一个发布-订阅通信闭环。本案例将定义Address结构体(包含街道、城市)与Person结构体(包含姓名、年龄、地址),通过FastDDS的IDL编译器生成类型支持代码,实现一个发布者周期发送Person消息、订阅者接收并打印的完整示例。

环境与依赖

  • FastDDS ≥ 2.6(含fastddsgen工具)
  • C++11及以上编译器,CMake ≥ 3.16
  • 需安装FastDDS Python绑定(用于fastddsgen)或使用FastDDS提供的IDL编译器

实现步骤

整体分为三步:编写IDL定义嵌套类型 → 使用fastddsgen生成C++类型支持代码 → 编写发布订阅主程序并编译运行。

1. 定义IDL文件(person.idl)

struct Address {
    string street;
    string city;
    uint16 zip_code;
};

struct Person {
    string name;
    uint16 age;
    Address address;
};

2. 生成类型支持代码

fastddsgen person.idl

该命令生成person.h、person.cxx、PersonPubSubTypes.h、PersonPubSubTypes.cxx等文件,其中包含嵌套类型的序列化/反序列化实现。

3. 发布订阅主程序(main.cpp)

#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/rtps/transport/TCPv4TransportDescriptor.hpp>
#include <thread>
#include <chrono>
#include <iostream>
#include "personPubSubTypes.h"

using namespace eprosima::fastdds::dds;

class SubListener : public DataReaderListener {
public:
    void on_data_available(DataReader* reader) override {
        Person sample;
        SampleInfo info;
        while (reader->take_next_sample(&sample, &info) == ReturnCode_t::RETCODE_OK) {
            if (info.valid_data) {
                std::cout << "Received: " << sample.name() << ", age=" << sample.age()
                          << ", addr=" << sample.address().street() << " "
                          << sample.address().city() << " " << sample.address().zip_code() << std::endl;
            }
        }
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " pub|sub" << std::endl;
        return 1;
    }
    bool is_pub = (std::string(argv[1]) == "pub");

    DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
    DomainParticipant* participant = DomainParticipantFactory::get_instance().create_participant(0, pqos);
    if (!participant) return 1;

    TypeSupport type(new PersonPubSubType());
    type.register_type(participant);

    Topic* topic = participant->create_topic("PersonTopic", "Person", TOPIC_QOS_DEFAULT);
    if (!topic) return 1;

    if (is_pub) {
        Publisher* pub = participant->create_publisher(PUBLISHER_QOS_DEFAULT);
        DataWriter* writer = pub->create_datawriter(topic, DATAWRITER_QOS_DEFAULT);
        if (!writer) return 1;

        Person msg;
        msg.name("Alice");
        msg.age(30);
        msg.address().street("123 Main St");
        msg.address().city("Springfield");
        msg.address().zip_code(12345);

        for (int i = 0; i < 10; ++i) {
            msg.age(30 + i);
            writer->write(&msg);
            std::cout << "Sent: " << msg.name() << " age=" << msg.age() << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }
    } else {
        Subscriber* sub = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT);
        SubListener listener;
        DataReader* reader = sub->create_datareader(topic, DATAREADER_QOS_DEFAULT, &listener);
        if (!reader) return 1;
        std::this_thread::sleep_for(std::chrono::seconds(6));
    }

    participant->delete_contained_entities();
    DomainParticipantFactory::get_instance().delete_participant(participant);
    return 0;
}

4. CMake配置(CMakeLists.txt)

cmake_minimum_required(VERSION 3.16)
project(nested_dds)

find_package(fastdds REQUIRED)
find_package(fastcdr REQUIRED)

add_executable(nested_pubsub main.cpp person.cxx personPubSubTypes.cxx)
target_link_libraries(nested_pubsub fastdds fastcdr)

运行与验证

# 编译
mkdir build && cd build
cmake ..
make

# 终端1:启动订阅者
./nested_pubsub sub

# 终端2:启动发布者
./nested_pubsub pub

通过条件:订阅者终端依次打印出10条包含姓名、年龄、街道、城市和邮编的完整消息,且年龄从30递增到39,即证明嵌套结构体的序列化、传输和反序列化正确。

关键说明

  • 嵌套结构体在IDL中直接使用成员变量声明,fastddsgen自动生成对应的访问方法(如address()返回引用),无需手动编写序列化代码。
  • 发布者与订阅者必须使用相同的Domain ID(此处为0)和Topic名称(PersonTopic),且类型名Person需与IDL定义一致。
  • 若需跨机器通信,需配置发现协议或使用TCP传输,本案例默认使用共享内存/UDP本地通信。