从ROS2到自动驾驶:手把手教你用FastDDS搭建第一个机器人通信节点(附避坑指南)
从ROS2到自动驾驶:手把手教你用FastDDS搭建第一个机器人通信节点(附避坑指南)
在机器人开发领域,通信中间件的选择往往决定了整个系统的实时性和可靠性。当ROS2决定采用DDS作为其默认通信框架时,FastDDS凭借其开源特性和高性能表现,迅速成为开发者关注的焦点。本文将带你从零开始,在Ubuntu系统中搭建一个基于FastDDS的简单发布/订阅通信节点,并分享实际项目中积累的配置技巧和常见问题解决方案。
1. 环境准备与FastDDS安装
在开始编码之前,我们需要确保开发环境正确配置。FastDDS作为ROS2的默认中间件之一,其安装过程需要特别注意版本兼容性。
1.1 系统要求与依赖安装
推荐使用Ubuntu 20.04或22.04 LTS版本,这些版本被主流ROS2发行版官方支持。首先安装基础编译工具和依赖项:
sudo apt update
sudo apt install -y build-essential cmake git wget
sudo apt install -y libssl-dev libasio-dev libtinyxml2-dev
FastDDS需要Java环境支持其IDL编译器,建议安装OpenJDK 11:
sudo apt install -y openjdk-11-jdk
1.2 源码编译安装FastDDS
从GitHub获取最新稳定版源码(本文以2.10.1为例):
git clone --recursive https://github.com/eProsima/Fast-DDS.git
cd Fast-DDS
git checkout v2.10.1
mkdir build && cd build
配置编译选项时,建议启用性能优化和安全特性:
cmake -DCMAKE_BUILD_TYPE=Release \
-DTHIRDPARTY=ON \
-DSECURITY=ON ..
make -j$(nproc)
sudo make install
注意:编译过程可能耗时较长,-j参数可根据CPU核心数调整。安装完成后建议执行
ldconfig更新动态链接库缓存。
1.3 验证安装
创建简单的测试程序验证安装是否成功:
// test_fastdds.cpp
#include <fastdds/dds/domain/DomainParticipant.hpp>
int main()
{
eprosima::fastdds::dds::DomainParticipant* participant =
eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->create_participant(0);
if(participant != nullptr) {
std::cout << "FastDDS安装成功!" << std::endl;
participant->delete_contained_entities();
}
return 0;
}
编译并运行测试程序:
g++ test_fastdds.cpp -o test_fastdds -lfastcdr -lfastrtps
./test_fastdds
2. 第一个FastDDS通信示例
理解了基础概念后,让我们实现一个完整的发布/订阅示例。这个例子将展示如何创建DomainParticipant、定义Topic、配置Publisher和Subscriber。
2.1 定义数据类型
首先需要定义通信中使用的数据结构。FastDDS使用IDL(接口定义语言)来描述数据类型:
// HelloWorld.idl
module hello {
struct HelloWorld {
unsigned long index;
string message;
};
};
使用FastDDS提供的IDL编译器生成C++代码:
fastddsgen HelloWorld.idl
这将生成HelloWorld.h和HelloWorld.cxx文件,包含序列化和反序列化所需的代码。
2.2 发布者实现
发布者代码需要完成以下步骤:
- 创建DomainParticipant
- 注册数据类型
- 创建Publisher
- 创建Topic
- 创建DataWriter
- 定期发布数据
// publisher.cpp
#include "HelloWorld.h"
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/topic/Topic.hpp>
class HelloWorldPublisher {
public:
HelloWorldPublisher() : participant_(nullptr), publisher_(nullptr),
topic_(nullptr), writer_(nullptr), sample_(new HelloWorld()) {
sample_->index(0);
sample_->message("Hello from FastDDS!");
}
bool init() {
// 创建DomainParticipant
participant_ = eprosima::fastdds::dds::DomainParticipantFactory::get_instance()
->create_participant(0);
if (!participant_) return false;
// 注册数据类型
eprosima::fastdds::dds::TypeSupport type(new HelloWorldPubSubType());
type.register_type(participant_);
// 创建Publisher
publisher_ = participant_->create_publisher(
eprosima::fastdds::dds::PUBLISHER_QOS_DEFAULT);
if (!publisher_) return false;
// 创建Topic
topic_ = participant_->create_topic(
"HelloWorldTopic", type.get_type_name(),
eprosima::fastdds::dds::TOPIC_QOS_DEFAULT);
if (!topic_) return false;
// 创建DataWriter
writer_ = publisher_->create_datawriter(
topic_, eprosima::fastdds::dds::DATAWRITER_QOS_DEFAULT);
return writer_ != nullptr;
}
void run(uint32_t samples) {
for (uint32_t i = 0; i < samples; ++i) {
sample_->index(i);
writer_->write(sample_.get());
std::cout << "发送: " << sample_->message() << " " << sample_->index() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
private:
eprosima::fastdds::dds::DomainParticipant* participant_;
eprosima::fastdds::dds::Publisher* publisher_;
eprosima::fastdds::dds::Topic* topic_;
eprosima::fastdds::dds::DataWriter* writer_;
std::unique_ptr<HelloWorld> sample_;
};
2.3 订阅者实现
订阅者需要实现类似的初始化步骤,并添加数据到达时的回调处理:
// subscriber.cpp
#include "HelloWorld.h"
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/topic/Topic.hpp>
class HelloWorldSubscriber {
public:
HelloWorldSubscriber() : participant_(nullptr), subscriber_(nullptr),
topic_(nullptr), reader_(nullptr) {}
~HelloWorldSubscriber() {
if (reader_ != nullptr) subscriber_->delete_datareader(reader_);
if (topic_ != nullptr) participant_->delete_topic(topic_);
if (subscriber_ != nullptr) participant_->delete_subscriber(subscriber_);
if (participant_ != nullptr)
eprosima::fastdds::dds::DomainParticipantFactory::get_instance()
->delete_participant(participant_);
}
bool init() {
// 创建DomainParticipant
participant_ = eprosima::fastdds::dds::DomainParticipantFactory::get_instance()
->create_participant(0);
if (!participant_) return false;
// 注册数据类型
eprosima::fastdds::dds::TypeSupport type(new HelloWorldPubSubType());
type.register_type(participant_);
// 创建Subscriber
subscriber_ = participant_->create_subscriber(
eprosima::fastdds::dds::SUBSCRIBER_QOS_DEFAULT);
if (!subscriber_) return false;
// 创建Topic
topic_ = participant_->create_topic(
"HelloWorldTopic", type.get_type_name(),
eprosima::fastdds::dds::TOPIC_QOS_DEFAULT);
if (!topic_) return false;
// 创建DataReader
eprosima::fastdds::dds::DataReaderQos reader_qos =
eprosima::fastdds::dds::DATAREADER_QOS_DEFAULT;
reader_ = subscriber_->create_datareader(
topic_, reader_qos, this);
return reader_ != nullptr;
}
void on_data_available(
eprosima::fastdds::dds::DataReader* reader) {
HelloWorld msg;
eprosima::fastdds::dds::SampleInfo info;
if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK) {
if (info.valid_data) {
std::cout << "收到: " << msg.message() << " " << msg.index() << std::endl;
}
}
}
void run() {
std::cout << "订阅者运行中,等待数据..." << std::endl;
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
private:
eprosima::fastdds::dds::DomainParticipant* participant_;
eprosima::fastdds::dds::Subscriber* subscriber_;
eprosima::fastdds::dds::Topic* topic_;
eprosima::fastdds::dds::DataReader* reader_;
};
3. 与ROS2集成实战
FastDDS作为ROS2的默认中间件之一,了解如何直接使用FastDDS可以帮助我们更好地理解和优化ROS2系统。
3.1 配置ROS2使用特定RMW实现
ROS2支持多种DDS实现,可以通过环境变量指定使用的中间件:
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
验证当前使用的RMW实现:
ros2 doctor --report | grep "RMW implementation"
3.2 自定义FastDDS XML配置
FastDDS支持通过XML文件进行详细配置,这对于性能调优特别有用。创建一个名为 fastdds_config.xml 的文件:
<?xml version="1.0" encoding="UTF-8" ?>
<dds>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<transport_descriptors>
<transport_descriptor>
<transport_id>udp_transport</transport_id>
<type>UDPv4</type>
<sendBufferSize>65536</sendBufferSize>
<receiveBufferSize>65536</receiveBufferSize>
</transport_descriptor>
</transport_descriptors>
<participant profile_name="custom_participant">
<rtps>
<userTransports>
<transport_id>udp_transport</transport_id>
</userTransports>
<useBuiltinTransports>false</useBuiltinTransports>
<builtin>
<discovery_config>
<leaseDuration>
<sec>3</sec>
</leaseDuration>
<leaseAnnouncement>
<sec>1</sec>
</leaseAnnouncement>
</discovery_config>
</builtin>
</rtps>
</participant>
</profiles>
</dds>
通过环境变量指定配置文件:
export FASTRTPS_DEFAULT_PROFILES_FILE=fastdds_config.xml
3.3 性能优化技巧
- 共享内存传输 :对于同一主机上的通信,使用共享内存可以大幅提升性能
- 零拷贝优化 :利用FastDDS的零拷贝特性减少内存复制
- QoS策略调整 :根据应用场景调整可靠性、持久性等QoS参数
共享内存配置示例:
<transport_descriptor>
<transport_id>shm_transport</transport_id>
<type>SHM</type>
<segment_size>16777216</segment_size>
</transport_descriptor>
4. 常见问题与解决方案
在实际项目中使用FastDDS时,开发者常会遇到一些典型问题。以下是经过验证的解决方案。
4.1 编译与链接问题
问题1:找不到FastDDS库
error while loading shared libraries: libfastrtps.so.2: cannot open shared object file
解决方案:
sudo ldconfig
如果问题依旧,检查库文件是否在标准路径中:
find /usr -name "libfastrtps*"
问题2:类型注册冲突
Type already registered
解决方案:确保类型名称唯一,或在程序退出时正确注销类型。
4.2 运行时问题
问题1:发布者与订阅者无法通信
检查步骤:
- 确认双方使用相同的Domain ID
- 验证网络连接(特别是多机通信时)
- 检查防火墙设置
- 确认双方使用相同的Topic名称和数据类型
问题2:高延迟或数据丢失
优化建议:
- 调整发送缓冲区大小
- 使用更高效的传输协议(如共享内存)
- 优化QoS策略,平衡可靠性与性能
4.3 ROS2集成问题
问题1:ROS2节点无法发现彼此
解决方案:
- 确认所有节点使用相同的DDS实现
- 检查ROS_DOMAIN_ID设置是否一致
- 验证网络组播是否正常工作
# 测试组播通信
ping 239.255.0.1
问题2:ROS2与原生FastDDS节点互通
实现要点:
- 确保使用相同的数据类型定义
- 配置兼容的QoS策略
- 使用相同的Domain ID
数据类型转换示例(ROS2 msg ↔ FastDDS):
// 将ROS2消息转换为FastDDS类型
void convert(const std_msgs::msg::String& ros_msg, HelloWorld& dds_msg) {
dds_msg.message(ros_msg.data);
}
// 将FastDDS类型转换为ROS2消息
void convert(const HelloWorld& dds_msg, std_msgs::msg::String& ros_msg) {
ros_msg.data = dds_msg.message();
}
更多推荐


所有评论(0)