很多人第一次尝试源码安装Fast DDS,都会以为这就是标准的cmake三步走:mkdir build、cmake、make install。真跑起来才发现,光一个configure就能卡住半天,报错信息里一半是foonathan_memory,一半是fastcdr,还有个Java只有在用到Fast DDS Gen的时候才跳出来捣乱。这些年帮不同项目配过gsoap在Windows下的源码编译、USRP UHD驱动的源码安装、nginx的源码部署,每个库都有自己的脾气。Fast DDS在这堆库里算比较典型的一种:它不是一个孤立软件包,而是一串互相依赖的仓库。
这篇手顺把Fast DDS源码安装这条路从头到尾捋一遍:先解释为什么需要源码编译,再把依赖关系和环境准备说清楚,然后是三个库的编译顺序和CMake开关,装完之后的验证方法,最后附上我实际踩过的坑和处理方式。适合ROS 2开发者、机器人项目成员,以及任何想在嵌入式平台或自定义环境里拿到最新Fast DDS的人。照着走,正常情况下一次能过。
1. 为什么源码安装Fast DDS,而不是apt一把梭
1.1 apt版本和源码版本的差距
Fast DDS在Ubuntu仓库里对应的包名是libfastrtps-dev(部分新发行版改叫libfastdds-dev),装上确实省事,一条apt命令搞定。但等你真正做项目就会发现,发行版自带的版本往往落后官方好几个大版本。以Ubuntu 22.04为例,apt源里的Fast DDS是2.6.x,而官方稳定分支已经走到2.14.x、3.x,很多新功能、性能优化和bug修复你根本拿不到。
更关键的场景是这几类:第一,你需要开启默认编译里没有的模块,比如安全通信SECURITY、或者用于压测的perf工具;第二,项目要部署到ARM嵌入式板子上,apt的架构支持可能不完整,交叉编译只能靠源码;第三,你要在Fast DDS协议栈上做二次开发,或者排查一个只在特定版本出现的协议级问题,这种情况下没有源码根本没法下手。
还有一个很多人忽略的场景:你的厂商SDK或算法库可能依赖某个指定的Fast DDS版本,而apt版本跟你依赖链里其他库冲突。源码安装配上独立安装前缀,是解决这种版本冲突最干净的手段。所有这些需求,最后都会指向同一条路——从源码编译。
1.2 Fast DDS是什么,以及它背后的一串仓库
先给没接触过DDS的读者用一句话解释:DDS(Data Distribution Service)是OMG定义的实时数据分发中间件标准,它让分布式系统里的各个节点无需知道彼此的网络地址,就能通过"主题"订阅和发布数据。Fast DDS是eProsima用C++实现的DDS协议栈,同时也是ROS 2默认的通信中间件,大量机器人、自动驾驶项目都在用。
源码安装Fast DDS,你面对的不是一个仓库,而是一个仓库群。核心编译链路有三个:
| 仓库 | 作用 | 在整条链里的位置 |
|---|---|---|
| foonathan_memory_vendor | 高性能内存分配器 | Fast DDS收发数据时的内存池来源 |
| Fast-CDR | CDR序列化库 | 把结构体数据编码成网络字节流再解码回来 |
| Fast-DDS | DDS/RTPS协议实现 | 发布订阅、发现机制、QoS策略都在这里 |
除了这三个编译期必须的仓库,还有可选的:Fast-DDS-Gen负责把IDL接口文件生成C++代码,编译它需要Java环境;fastdds_python是Python绑定,需要时再单独看。搞清楚这个依赖群,后面每一步都不会慌。
2. 编译前的系统环境准备:装少一个包都过不去
2.1 Ubuntu/Debian下的一键依赖安装
我以Ubuntu 20.04/22.04为例,先把系统依赖一条命令装齐:
sudo apt update sudo apt install -y build-essential cmake git \ libasio-dev libtinyxml2-dev libssl-dev \ openjdk-11-jre-headless逐个说下这些都是干嘛的,免得装完不知道为什么:
- build-essential:gcc、g++、make的合集,没有它后面什么都编不了。
- cmake:Fast DDS的构建系统,2.x系列要求3.16以上。
- git:拉源码、切tag用。
- libasio-dev:Fast DDS的RTPS网络层用Asio做异步I/O,这是它网络收发的底层依赖。
- libtinyxml2-dev:Fast DDS的XML Profile解析库。DDS参数比较多,官方支持用XML文件描述QoS策略和参与者配置,就靠这个库解析。
- libssl-dev:只在开启SECURITY模块时需要,建议直接装上,反正不大。
- openjdk-11-jre-headless:严格说编译Fast DDS核心用不到Java,但后面你十有八九会用Fast-DDS-Gen生成接口代码,那时候没Java就直接卡死,顺手装了省得回头补。
如果你确定这辈子都不碰Fast-DDS-Gen,可以把最后的Java包去掉,编译核心库完全不受影响。
2.2 工具链版本红线与升级方法
版本问题在源码编译里是头号暗雷。Ubuntu 20.04默认的CMake是3.16.3,正好踩在Fast DDS 2.x的最低线上,能用但不太宽裕;Ubuntu 18.04默认CMake 3.10就彻底不够了,configure阶段会直接报版本太低。这时候有几个办法:加Kitware的apt源装新版CMake、用pip在虚拟环境里装、或者去cmake.org下载官方二进制包。我个人用得最多的是Kitware源,因为它跟系统apt管理方式一致,后续升级方便。
# 检查三件套版本 cmake --version g++ --version java -version编译器方面,GCC 7.5以上编译Fast DDS 2.x都没问题,Ubuntu 20.04的GCC 9、22.04的GCC 11我都实测过。如果编译过程中遇到莫名其妙的模板报错,先排查编译器版本是不是太老,再怀疑代码本身。Ubuntu 18.04还有个隐藏坑:默认的libasio-dev版本偏低,个别情况下异步网络层会报编译错误,尽量用apt upgrade把相关包升到发行版支持的最新版。
3. 底层依赖编译:foonathan_memory与Fast CDR
3.1 foonathan_memory_vendor编译
这个库给Fast DDS提供内存池分配机制。命令放出来:
git clone https://github.com/eProsima/foonathan_memory_vendor.git cd foonathan_memory_vendor mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON .. cmake --build . --target install -- -j$(nproc)两个参数值得说明。-DCMAKE_INSTALL_PREFIX=/usr/local在很多发行版下其实就是CMake的默认安装前缀,我建议每次编译都显式写出来,习惯之后想改成/opt/fastdds-2.14这类独立前缀时,就不会忘记它的存在。-DBUILD_SHARED_LIBS=ON则比较关键:这个库默认可能编成静态库,而Fast DDS本体和你的应用都要链接它,静态库容易把分配器符号重复带入多个动态库,真出问题的时候,表现就是运行时莫名其妙的内存崩溃,排查起来特别痛苦。共享库是更省心的选择。
3.2 Fast CDR编译与版本匹配表
Fast CDR是Fast DDS的序列化底层,负责把C++结构体编码成CDR格式的字节流。编译本身很简单:
git clone https://github.com/eProsima/Fast-CDR.git cd Fast-CDR git checkout v1.1.0 mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. cmake --build . --target install -- -j$(nproc)但这里的版本匹配是整个源码安装流程里最容易踩的坑。Fast CDR和Fast DDS的版本是耦合的,不能随手拉最新的Fast CDR去配一个老版本的Fast DDS。我整理了一张对应表:
| Fast DDS版本 | Fast CDR版本 | 备注 |
|---|---|---|
| 2.6.x | 1.0.x | ROS 2 Humble同款组合 |
| 2.10.x ~ 2.14.x | 1.1.x | 常见的稳定组合 |
| 3.0.x 及以上 | 2.2.x 及以上 | 新大版本的耦合关系 |
怎么确认你要的组合?最简单的方法是打开Fast-DDS仓库的CMakeLists.txt,搜fastcdr,里面会写清楚它要求的版本范围。版本不匹配的后果很直接:要么configure阶段报错,要么编译链接时出现一堆undefined reference,要么运行时告诉你libfastcdr.so.1: version 'FastCDR_1.1' not found。
注意:Fast CDR和Fast DDS的版本对应,要以Fast-DDS仓库里CMakeLists.txt的find_package要求为准,不要凭印象选。
3.3 版本拉取的正确姿势
源码安装的核心原则是固定版本。我见过太多人clone默认分支,过几天重新拉代码,依赖关系就悄悄变了。规范做法是用--branch参数直接拉指定tag:
git clone --branch v2.14.0 https://github.com/eProsima/Fast-DDS.git git clone --branch v1.1.0 https://github.com/eProsima/Fast-CDR.git git clone https://github.com/eProsima/foonathan_memory_vendor.git先看仓库里有哪些tag,可以用git ls-remote --tags,或者clone之后执行git tag。选一个已知的稳定组合,比如Fast DDS 2.14.0配Fast CDR 1.1.0,然后在文档或笔记里记录下来。项目上线后想复现构建环境,这套记录就是你的救命稻草。
4. Fast DDS本体编译:开关参数与两种方式
4.1 推荐CMake配置逐项解读
进入Fast-DDS仓库,创建build目录,执行:
cd Fast-DDS git checkout v2.14.0 mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCOMPILE_TOOLS=ON \ -DSECURITY=ON \ -DCOMPILE_EXAMPLES=ON \ .. cmake --build . --target install -- -j$(nproc)逐项拆解这些开关,每一个都有实际影响:
CMAKE_BUILD_TYPE=Release:这个我建议必开。默认不设置的话,编译器基本不做优化,DDS的收发性能会明显下降。做机器人或实时通信的场景,性能差一点可能就是几千条消息的吞吐差距。COMPILE_TOOLS=ON:编译出fastdds命令行工具。它有discovery、shm等子命令,后面验证安装、排查网络问题时是直接帮手,强烈建议打开。SECURITY=ON:启用DDS安全模块,包括身份认证和传输加密。前提是装了libssl-dev。项目涉及安全敏感数据就开,否则可以关掉省一点编译时间。COMPILE_EXAMPLES=ON:编译官方示例程序,验证安装时直接用,不用自己写测试代码。对新手来说这个开关价值非常高。
编译时长取决于机器性能。8核机器上编Fast DDS本体,Release模式大概5到10分钟,首次编译如果还带了测试依赖(googletest)会再久一点。中途如果某个源文件报编译错误,先别急着重来,把报错贴到搜索里查一下,多半是编译器版本或依赖缺失问题。
4.2 安装落位后的三分钟检查
编译安装完成后,花三分钟确认东西都到位了,别急着进下一步:
ls /usr/local/lib | grep -E "fastdds|fastcdr|foonathan" ls /usr/local/include/fastdds/ | head sudo ldconfig fastdds --helpsudo ldconfig这一步很多人会漏。Fast DDS把共享库装到/usr/local/lib,而多数Linux发行版的默认动态库搜索路径里没有这个目录,不执行ldconfig,运行时就会报error while loading shared libraries。执行完ldconfig后,再确认fastdds命令能打出帮助信息,说明工具链和动态库基本没问题。
4.3 colcon方式:ROS 2用户更顺手的路线
如果你的环境里已经装了ROS 2,或者将来一定会装,我更推荐用colcon工作区方式把Fast DDS这一整套仓库编到一起:
mkdir -p ~/fastdds_ws/src && cd ~/fastdds_ws/src git clone --branch v2.14.0 https://github.com/eProsima/Fast-DDS.git git clone --branch v1.1.0 https://github.com/eProsima/Fast-CDR.git git clone https://github.com/eProsima/foonathan_memory_vendor.git cd ~/fastdds_ws colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release source install/setup.bashcolcon会按照CMake依赖关系自动排序编译,不用手动管理编译顺序,安装结果全部隔离在~/fastdds_ws/install里,不污染系统目录,想要多版本并存也容易——多建几个工作区就行。
但这里有个必须提醒的坑:不要在一个shell里同时source ROS 2的apt环境(/opt/ros下的setup.bash)和你自己的fastdds_ws。两套Fast DDS库如果同时在环境变量里生效,轻则版本错乱,重则运行时崩溃。正确做法是分开终端、分开用途,或者干脆用colcon一路从源码把ROS 2相关包也编进来。
5. 装完验证:让第一个DDS消息跑起来
5.1 检查动态库、环境变量和CLI
安装验证从最底层开始。先确认环境变量和动态库加载没问题:
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ldconfig -p | grep -E "fastdds|fastcdr" which fastdds如果which fastdds找不到,检查/usr/local/bin是否在PATH里,再看看编译时COMPILE_TOOLS是否真的开了。如果你用的是colcon方式,上面这些路径应该指向~/fastdds_ws/install,别混了。环境变量持久化也别偷懒,写进~/.bashrc:
echo 'export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc5.2 HelloWorld示例:发布订阅一次通过
编译时开了COMPILE_EXAMPLES,构建目录里已经有现成的官方示例。找到HelloWorldExample,开两个终端跑:
# 终端一:发布端 cd Fast-DDS/build/examples/cpp/dds/HelloWorldExample ./HelloWorldExample publisher # 终端二:订阅端 cd Fast-DDS/build/examples/cpp/dds/HelloWorldExample ./HelloWorldExample subscriber正常情况下,发布端会持续输出发送的消息和序号,订阅端几乎同时收到并打印出来。这个结果说明两件事:RTPS协议的自动发现机制工作正常(两个进程不需要互相知道IP和端口就建立了连接),数据收发链路完整(序列化和网络传输都没问题)。跑一会儿之后Ctrl+C停掉就行。
如果订阅端收不到任何消息,优先检查两台机器(或两个终端)是不是在一个网段、防火墙有没有挡UDP端口。Fast DDS的默认发现走的是UDP组播,跨网段或组播受限的环境要额外配置发现服务器或指定网卡。
5.3 用fastdds discovery确认网络发现能力
较新的Fast DDS(2.10以后)提供了统一的CLI工具,里头的discovery命令可以启动一个Discovery Server。在机器上执行:
fastdds discovery --server-id 0 --ip-address 127.0.0.1 --port 11811能看到discovery server正常监听端口,就说明这个二进制和当前网络环境是配合的。这里解释一下原理:默认情况下Fast DDS用UDP组播做P2P发现,相当于节点们在楼道里喊话找人;Discovery Server模式则是大家先到一个"通讯录登记处"报到,再由它告诉大家彼此在哪。后者在跨子网、复杂网络场景下非常有用。CLI能启动服务,网络栈基本就是健康的。
对于日常验证,5.2的发布订阅互通已经足够。discovery server更多是你在做跨网段部署时才需要深入研究的东西。
6. 源码安装排错实录:从configure到runtime的连锁坑
6.1 configure阶段找不到foonathan_memory或fastcdr
Fast DDS的CMake配置阶段最常见的报错长这样:
CMake Error at CMakeLists.txt:xx (find_package): Could not find a package configuration file provided by "foonathan_memory"排查链路是有固定顺序的。先确认依赖库是不是真的装上了:
ls /usr/local/lib/cmake/foonathan_memory ls /usr/local/lib/cmake/fastcdr如果目录不存在,说明前面章节的依赖编译没成功或没执行install。如果目录存在但configure依然找不到,多半是CMake没去/usr/local找,显式指过去就行:
cmake -DCMAKE_PREFIX_PATH=/usr/local ..这个错误本身不难修,真正坑人的是依赖库编译中途报错但是你没注意到,以为装好了。所以每编完一个库,我都建议执行一次ls确认产物,别急着进下一个。
6.2 Fast CDR版本错配的链接期爆炸
版本不匹配的坑往往不在configure阶段暴露,而是潜伏到链接期。常见报错是成片的undefined reference to 'eprosima::fastcdr::...',或者运行时提示libfastcdr.so.1: version 'FastCDR_1.1' not found。
这种问题的根因通常是:你源码编译的Fast DDS在找fastcdr时,找到了系统apt装的旧版本,而不是刚编译的新版本。处理办法分两步。编译阶段,在Fast-DDS的build目录里查一下实际用的fastcdr位置:
grep -i fastcdr CMakeCache.txt如果路径指向/usr/lib/x86_64-linux-gnu,说明新版本没被找到,手动指定:
cmake -Dfastcdr_DIR=/usr/local/lib/cmake/fastcdr ..运行阶段,老版本fastcdr.so.1和新版fastcdr.so.1可能文件名完全相同,靠LD_LIBRARY_PATH的路径顺序决定优先级。我的建议是:环境里既然有源码装的版本,就把apt装的libfastcdr系列包卸掉,前提是确认没有别的程序依赖它,从源头消灭错配。
注意:链接期报undefined reference,先查CMakeCache.txt里fastcdr_DIR的实际路径,再决定指哪边,不要盲目重装。
6.3 Fast DDS Gen的Java和Gradle问题
Fast DDS Gen是独立的IDL代码生成器,编译它需要Java环境。如果你用旧版JDK 8,大概率会看到UnsupportedClassVersionError,大意是Gradle或构建脚本是用新版Java编译的,你的Java太老跑不动。解决办法是装JDK 11或17:
sudo apt install -y openjdk-17-jdk-headless export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 java -versionFast DDS Gen第一次构建时,Gradle wrapper会下载大量依赖,对网络要求比较高。如果卡在下载阶段,先确认网络能访问Maven仓库,再考虑加镜像源。另外注意clone时要带子模块:
git clone --recursive https://github.com/eProsima/Fast-DDS-Gen.git cd Fast-DDS-Gen ./gradlew assemble少拉几个子模块的话,编译到一半会因为缺文件报出一堆无关错误,很容易把排查方向带偏。
6.4 运行时报libfastdds.so找不到
这个错误基本都发生在你编译成功、安装成功,但运行示例或自己程序的时候:
./HelloWorldExample: error while loading shared libraries: libfastdds.so: cannot open shared object file原因前面提过:/usr/local/lib不在系统动态库搜索路径。两条路都可以解决。一条是改系统级的ldconfig:
sudo ldconfig它会读取/etc/ld.so.conf.d下的配置重新生成缓存,把/usr/local/lib加进去之后,系统所有程序都能找到。另一条是shell级环境变量:
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH区别很明确:ldconfig是全局、持久的,适合正式环境;LD_LIBRARY_PATH是临时的、单个终端有效的,适合调试阶段。如果两条都试过还报错,用ldd命令看看到底加载了哪个路径的库:
ldd ./HelloWorldExample | grep fastddsldd输出的路径一目了然,能直接看出是不是被apt旧版本抢先了。
6.5 用install_manifest.txt实现可追溯卸载
源码安装最大的痛点是卸载。apt装的包用apt remove就行,源码装的没有任何包管理器记录。其实CMake已经替你留了一手:每次install之后,build目录里会生成install_manifest.txt,记录了所有安装的文件路径。想卸载时:
cd Fast-DDS/build sudo xargs rm < install_manifest.txt三个库的build目录各执行一次,系统基本能回到安装前的状态。这条经验我吃了不少亏才总结出来,早期直接删目录,删完才发现动态库链接信息还残留在ldconfig缓存里,各种诡异问题。用manifest删完后,再执行一次sudo ldconfig刷新缓存,才算干净。
我个人的体会是:Fast DDS源码安装的难度不在某一个具体命令,而在于整套依赖链的版本节奏。只要记住"固定版本、按序编译、独立前缀、验证链路"这四件事,基本不会出大问题。最后再分享一个小技巧——每次编译前,把三个仓库的commit号或tag写进项目的README,下次环境重建时照着恢复,比任何记忆都可靠。如果你是从ROS 2过来的,一定优先考虑colcon工作区方式,别把源码装的Fast DDS硬塞进apt管理的ROS 2环境里,版本冲突这种事,遇上一次就够你折腾半天的。