4. DDS UDP单播通信

任务目标

本案例使用Fast DDS在单个主机上通过UDP单播实现DDS发布/订阅通信。发布端周期发送温度数据,订阅端接收并打印,以此验证DDS在UDP单播模式下的基本数据通路。

环境依赖

需要安装Fast DDS及其依赖(如Asio、TinyXML2等)。在Ubuntu上可通过以下命令安装:

sudo apt install libfastdds-dev

完整实现

以下代码包含一个可编译运行的完整示例。发布端和订阅端在同一可执行文件中通过命令行参数区分启动。

// dds_udp_unicast.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/UDPv4TransportDescriptor.hpp>
#include <iostream>
#include <thread>
#include <chrono>

using namespace eprosima::fastdds::dds;

// 定义温度数据类型
class Temperature
{
public:
    float value = 0.0f;
};

// 类型支持类(简化版,实际需用fastddsgen生成)
class TemperaturePubSubType : public TopicDataType
{
public:
    TemperaturePubSubType()
    {
        setName("Temperature");
        m_typeSize = sizeof(float);
    }
    bool serialize(void* data, SerializedPayload_t* payload) override
    {
        Temperature* t = static_cast<Temperature*>(data);
        memcpy(payload->data, &t->value, sizeof(float));
        payload->length = sizeof(float);
        return true;
    }
    bool deserialize(SerializedPayload_t* payload, void* data) override
    {
        Temperature* t = static_cast<Temperature*>(data);
        memcpy(&t->value, payload->data, sizeof(float));
        return true;
    }
    uint32_t calculate_serialized_size(void*, DataRepresentationId_t) override { return sizeof(float); }
    void* createData() override { return new Temperature(); }
    void deleteData(void* data) override { delete static_cast<Temperature*>(data); }
    bool getKey(void*, InstanceHandle_t*, bool) override { return false; }
};

// 订阅端监听器
class SubListener : public DataReaderListener
{
public:
    void on_data_available(DataReader* reader) override
    {
        Temperature temp;
        SampleInfo info;
        if (reader->take_next_sample(&temp, &info) == ReturnCode_t::RETCODE_OK)
        {
            if (info.valid_data)
            {
                std::cout << "收到温度: " << temp.value << " °C" << std::endl;
            }
        }
    }
};

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

    // 创建参与者
    DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
    pqos.transport().user_transports.clear();
    auto udp_transport = std::make_shared<eprosima::fastdds::rtps::UDPv4TransportDescriptor>();
    udp_transport->sendBufferSize = 65536;
    udp_transport->receiveBufferSize = 65536;
    pqos.transport().user_transports.push_back(udp_transport);
    pqos.transport().use_builtin_transports = false;

    DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, pqos);
    if (!participant) { std::cerr << "创建参与者失败" << std::endl; return 1; }

    // 注册类型
    TemperaturePubSubType type;
    TypeSupport ts(&type);
    ts.register_type(participant);

    // 创建主题
    Topic* topic = participant->create_topic("TemperatureTopic", "Temperature", TOPIC_QOS_DEFAULT);
    if (!topic) { std::cerr << "创建主题失败" << std::endl; return 1; }

    if (is_pub)
    {
        // 发布端
        Publisher* pub = participant->create_publisher(PUBLISHER_QOS_DEFAULT);
        DataWriter* writer = pub->create_datawriter(topic, DATAWRITER_QOS_DEFAULT);
        Temperature temp;
        temp.value = 25.0f;
        for (int i = 0; i < 10; ++i)
        {
            temp.value += 0.5f;
            writer->write(&temp);
            std::cout << "发布温度: " << temp.value << " °C" << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
    else
    {
        // 订阅端
        Subscriber* sub = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT);
        SubListener listener;
        DataReader* reader = sub->create_datareader(topic, DATAREADER_QOS_DEFAULT, &listener);
        std::cout << "订阅端启动,等待数据..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(15));
    }

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

编译与运行

编译命令:

g++ -std=c++11 dds_udp_unicast.cpp -o dds_demo -lfastdds -lfastcdr

在两个终端分别运行:

# 终端1(订阅端)
./dds_demo sub

# 终端2(发布端)
./dds_demo pub

验证方法

观察订阅端终端是否打印出与发布端相同序列的温度值(25.5°C、26.0°C……30.0°C)。若订阅端在15秒内收到全部10条数据,且数值与发布端一致,则说明UDP单播DDS通信成功。

关键说明

本示例显式配置了UDPv4传输并禁用内置传输,确保通信走UDP单播。实际工程中数据类型通常由fastddsgen工具从IDL文件生成,此处为聚焦通信流程而简化了类型支持类的实现。