基于ubuntu系统安装使用Fast DDS

🕒 2025-12-06 📁 ROS系统 👤 laumy 🔥 52 热度

什么是Fast DDS

Fast DDS是一个高性能的”分布式通信中间库”,用来在不同进程、不同设备之间传输数据的。Fast DDS是用C++写的一个DDS实现,在同一台机器的多个进程、或者多态机器之间进行通信。可以类似于ROS2的发布和订阅主题,可以自动发现对端(不用手写IP列表),支持可靠/不可靠、实时性、历史缓存、持久化等各种Qos。

FastDDS与Socket、gRPC有什么区别?

  • socket:自己管连接、重连、协议、序列化、发现等。
  • gRPC:偏请求、响应,RPC的风格。
  • FastDDS:原生支持多对多发布订阅,内置自动发现(谁上来就能发现谁),有非常丰富的Qos做实时系统的调优。

安装FastDDS

基于ubuntu 系统源码式编译安装FastDDS环境。

~/FastDDS_ws/src$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.3 LTS
Release:    24.04
Codename:   noble

为了保持系统的干净,我们统一将fastdds相关的库和依赖安装到指定的目录。

export FAST_DDS_INSTALL_DIR=$HOME/fastdds_install

# 创建安装目录和源码构建目录
mkdir -p $FAST_DDS_INSTALL_DIR

安装基础工具

sudo apt update
sudo apt install -y \
    build-essential \
    cmake \
    git \
    libssl-dev \
    libasio-dev \
    libtinyxml2-dev \
    wget \
    zip

FastDDS依赖C++编译环境、Cmake以及网络和XML解析库,因此需要安装以上的工具。

安装核心依赖

FastDDS主要依赖Foonathan memory和Fast CDR,前面是因为FastDDS使用foonathan_memory进行高效的内存管理(这对于实时性至关重要)。后者是FastDDS会把结构体编程二进制流,用于处理数据的序列化。

(1)安装Foonathan Memory

# 创建一个工作目录
mkdir -p ~/FastDDS_ws/src
cd ~/FastDDS_ws/src

# 克隆仓库
git clone https://github.com/eProsima/foonathan_memory_vendor.git

# 编译并安装
cd foonathan_memory_vendor
mkdir build && cd build
# 关键参数:-DCMAKE_INSTALL_PREFIX 指定安装位置
cmake .. -DCMAKE_INSTALL_PREFIX=$FAST_DDS_INSTALL_DIR -DBUILD_SHARED_LIBS=ON

cmake --build . -j$(nproc)
cmake --build . --target install

(2)安装Fast CDR(序列化)

cd ~/FastDDS_ws/src
git clone https://github.com/eProsima/Fast-CDR.git
cd Fast-CDR
mkdir build && cd build

# 关键参数:除了安装路径,还需要通过 -DCMAKE_PREFIX_PATH 告诉它去哪找 foonathan_memory (如果有依赖)
cmake .. -DCMAKE_INSTALL_PREFIX=$FAST_DDS_INSTALL_DIR \
         -DCMAKE_PREFIX_PATH=$FAST_DDS_INSTALL_DIR

cmake --build . -j$(nproc)
cmake --build . --target install

编译安装Fast DDS核心库

FastDDS需要依赖上面两个库。

cd ~/FastDDS_ws/src
git clone https://github.com/eProsima/Fast-DDS.git
cd Fast-DDS
mkdir build && cd build

# 关键参数解释:
# -DCMAKE_INSTALL_PREFIX: 安装到我们自定义目录
# -DCMAKE_PREFIX_PATH: 告诉 CMake 去自定义目录里找 FastCDR 和 Foonathan
cmake .. -DCMAKE_INSTALL_PREFIX=$FAST_DDS_INSTALL_DIR \
         -DCMAKE_PREFIX_PATH=$FAST_DDS_INSTALL_DIR \
         -DSECURITY=ON \
         -DCOMPILE_EXAMPLES=ON

cmake --build . -j$(nproc)
cmake --build . --target install

配置代码生成器Fast-DDS-Gen

这个工具是永远将

# 1. 回到源码目录
cd ~/FastDDS_ws/src

# 2. 克隆仓库 (获取最新版,通常是 v4.x)
git clone --recursive https://github.com/eProsima/Fast-DDS-Gen.git fastddsgen

# 3. 进入目录
cd Fast-DDS-Gen

# 4. 编译 (会自动下载 Gradle 并构建,需要保持网络畅通)
./gradlew assemble

环境加载

因为我们没有安装到系统默认路径,需要设置一下环境变量。

vim $FAST_DDS_INSTALL_DIR/setup.bash

#!/bin/bash

# 获取脚本所在的目录 (即 ~/fastdds_install)
INSTALL_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)

# 1. 告诉系统去哪里找 .so 动态库 (运行时必须)
export LD_LIBRARY_PATH=$INSTALL_ROOT/lib:$LD_LIBRARY_PATH

# 2. 告诉 CMake 去哪里找 find_package(FastDDS) (编译时必须)
export CMAKE_PREFIX_PATH=$INSTALL_ROOT:$CMAKE_PREFIX_PATH

# 3. 将 bin 加入 PATH,这样你可以直接输 fastddsgen (工具使用)
export PATH=$INSTALL_ROOT/bin:$PATH

echo "Fast DDS environment sourced from: $INSTALL_ROOT"

验证与使用

source ~/fastdds_install/setup.bash

mkdir -p ~/FastDDS_ws/src/SimpleChat
cd ~/FastDDS_ws/src/SimpleChat

(1)定义数据结构

// Chat.idl
struct ChatMessage
{
    unsigned long user_id;
    string message;
};

(2)生成胶水代码

fastddsgen Chat.idl

(3)编写发布者

创建 ChatPublisher.cpp。这个程序负责发送消息。

// ChatPublisher.cpp
#include "ChatPubSubTypes.hpp"

#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 <thread>
#include <iostream>

using namespace eprosima::fastdds::dds;

int main() {
    // 1. 创建 Participant (参与者)
    DomainParticipantQos participant_qos = PARTICIPANT_QOS_DEFAULT;
    DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, participant_qos);

    if (!participant) return 1;

    // 2. 注册数据类型
    TypeSupport type(new ChatMessagePubSubType());
    type.register_type(participant);

    // 3. 创建 Topic
    Topic* topic = participant->create_topic("ChatTopic", type.get_type_name(), TOPIC_QOS_DEFAULT);

    // 4. 创建 Publisher
    Publisher* publisher = participant->create_publisher(PUBLISHER_QOS_DEFAULT);

    // 5. 创建 DataWriter
    DataWriter* writer = publisher->create_datawriter(topic, DATAWRITER_QOS_DEFAULT);

    // 6. 循环发送数据
    ChatMessage data;
    data.user_id(101); // 假设我是用户 101

    int count = 0;
    while (true) {
        data.message("Hello FastDDS " + std::to_string(count++));

        writer->write(&data);
        std::cout << "[Sent] " << data.message() << std::endl;

        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    // 清理资源 (实际代码中通常在析构或信号处理中做)
    // participant->delete_contained_entities();
    // DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}

(4)编写订阅者

创建 ChatSubscriber.cpp。这个程序负责监听并打印消息。

// ChatSubscriber.cpp
#include "ChatPubSubTypes.hpp"

#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/core/ReturnCode.hpp>

#include <thread>
#include <chrono>
#include <iostream>

using namespace eprosima::fastdds::dds;

// 监听器类:回调函数在这里触发
class MyListener : public DataReaderListener {
public:
    void on_data_available(DataReader* reader) override {
        ChatMessage data;
        SampleInfo info;

        // 取出数据
        while (reader->take_next_sample(&data, &info) == eprosima::fastdds::dds::RETCODE_OK) {
            if (info.valid_data) {
                std::cout << "[Received] User " << data.user_id() 
                          << ": " << data.message() << std::endl;
            }
        }
    }

    void on_subscription_matched(DataReader* reader, const SubscriptionMatchedStatus& info) override {
        if (info.current_count_change == 1) {
            std::cout << ">> Match Found: Publisher connected!" << std::endl;
        } else if (info.current_count_change == -1) {
            std::cout << ">> Match Lost: Publisher disconnected." << std::endl;
        }
    }
};

int main() {
    // 1. 创建 Participant
    DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);

    // 2. 注册类型
    TypeSupport type(new ChatMessagePubSubType());
    type.register_type(participant);

    // 3. 创建 Topic
    Topic* topic = participant->create_topic("ChatTopic", type.get_type_name(), TOPIC_QOS_DEFAULT);

    // 4. 创建 Subscriber
    Subscriber* subscriber = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT);

    // 5. 创建 DataReader 并绑定 Listener
    MyListener listener;
    // 注意:这里需要传入 Listener 指针,并掩码设置我们要监听所有状态
    DataReader* reader = subscriber->create_datareader(topic, DATAREADER_QOS_DEFAULT, &listener);

    std::cout << "Waiting for messages... (Press Ctrl+C to stop)" << std::endl;

    // 阻塞主线程,否则 main 结束程序就退出了
    while(true) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    return 0;
}

(5)编写CmakeList

cmake_minimum_required(VERSION 3.10)
project(SimpleChat)

set(CMAKE_CXX_STANDARD 11)

find_package(FastDDS REQUIRED)

# --- 关键修改:匹配你 tree 里的文件 ---
# Chat.hpp 是头文件,不需要加入 source list
# 加入 ChatPubSubTypes.cxx 和 ChatTypeObjectSupport.cxx
set(GENERATED_SOURCES 
    ChatPubSubTypes.cxx 
    ChatTypeObjectSupport.cxx
)

add_executable(ChatPublisher ChatPublisher.cpp ${GENERATED_SOURCES})
target_link_libraries(ChatPublisher fastdds)

add_executable(ChatSubscriber ChatSubscriber.cpp ${GENERATED_SOURCES})
target_link_libraries(ChatSubscriber fastdds)

(6)编译运行

cd build
cmake ..
make -j4

./ChatSubscriber &

./ChatPublisher

发表你的看法

\t