ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ROS2 DDS选型与QoS配置实战:从原理到排坑

ROS2 DDS选型与QoS配置实战:从原理到排坑 ROS2 从 ROS1 走过来很多人一开始最不适应、也最容易踩坑的就是它底层的 DDS 换了通信机制也跟着变了。ROS1 时代我们几乎不用关心消息怎么传输topic 写好了、节点跑起来、rostopic echo 能看到数据这事就算通了。到了 ROS2DDS 作为中间件被引入之后问题就多了起来为什么两个节点明明在同一台机器上却互相发现不了为什么发布端和订阅端 QoS 不一致就直接报错为什么换了一个 DDS 实现之后延迟和丢包表现差距这么大这篇文章就围绕 ROS2 里的 DDS 协议选型和 QoS 策略做一次完整总结把这些坑背后的原理讲清楚同时给出可以直接参考的配置方法和排查思路。适合所有做 ROS2 开发、做机器人通信系统设计、或者正在被 multi-machine 通信和大数据量传输折腾的开发者参考。1. 为什么 ROS2 要选 DDS通信设计的一次大换血1.1 ROS1 的通信短板ROS1 的通信模型是中心化的。所有的节点要互相通信必须依赖一个叫做 roscore 的 master 节点来做名字服务和连接建立。也就是说节点启动之后先要去 master 那里注册自己的 topic、service 信息再通过 master 的牵线搭桥才能和真正的通信对端建立 TCP 连接。这种设计在单个机器人、单台电脑上跑 demo 没什么问题但一旦进入真实场景问题就暴露得很明显。多机协同是 ROS1 最不好搞的事情。两个机器人之间要通信你得先让它们都能访问到同一台 master还要配置好网络让各个节点的主机名能互相解析。更麻烦的是单点故障问题master 挂了整个机器人系统里的所有 topic 通信都跟着断掉这在机器人这种对稳定性要求极高的场景里几乎不可接受。还有一个痛点是数据实时性。ROS1 的 TCP 通信在局域网里表现尚可但到了需要实时控制、或者跨站点传输的时候延迟、抖动、丢包重传这些因素都没有被上层抽象起来。开发者想对通信质量做精细控制几乎无从下手。实时机器人系统里一个传感器数据晚到 10ms可能就意味着控制指令已经过时了。这就是 ROS2 下定决心要换通信底层的原因。1.2 DDS 解决了什么DDSData Distribution Service是一套由 OMG 组织制定的分布式实时通信规范。它和 ROS1 的 TCP 直连模型有一个本质差别DDS 是去中心化的它采用发布订阅模型节点之间直接发现、直接通信不需要一个中心节点来管理全局的信息。DDS 的发现机制是分两步走的。第一步是参与者发现阶段每个 DDS DomainParticipant 启动后都会往 DDS 域里发送发现报文宣告自己的存在然后第二步是端点发现阶段参与者之间交换各自的 publisher、subscriber 信息如果发现彼此有匹配的 topic 和数据类型就建立通信链路。这种机制的好处是天生支持动态拓扑节点随时加入、随时退出系统不需要重启网络里的其他节点也能自动感知到变化。对机器人这种分布式系统来说DDS 带来的价值非常直接。首先是实时性DDS 的实现通常支持 RTPSReal-Time Publish-Subscribe协议在局域网内可以达到微秒到毫秒级的延迟对于机器人控制、传感器数据流这种场景已经够用。其次是可靠性DDS 的 QoS 策略允许你精确控制消息是只要尽力传输还是要可靠重传是要只在内存里存一份还是要持久化到磁盘这些策略组合起来基本可以覆盖从遥感到指令下发到日志记录的所有通信需求。1.3 RMW 抽象层的意义ROS2 没有强制绑定某一个具体的 DDS 实现而是在 DDS 之上加了一层 RMWROS Middleware Interface抽象层。你的 ROS2 代码不直接面对某个 DDS 厂商的 API而是通过 rclcpp、rclpy 这些 ROS2 客户端库去调用 RMW 接口再由 RMW 翻译成具体 DDS 实现的调用。这就是我们平时说的 RMW 实现。这意味着你可以像切换后端一样切换 DDS。ROS2 官方维护了好几种 RMW 实现包括 Fast DDS、Cyclone DDS、RTI Connext DDS、GurumDDS 等。你在启动节点之前设置一个环境变量就能让整个 ROS2 系统跑在另一种 DDS 实现上。这个抽象层还有一个好处就是如果你对默认的 DDS 性能不满意可以只改配置、不改业务代码就完成整条通信链路的替换。我在实际项目里就频繁切换过 Fast DDS 和 Cyclone DDS切换之后节点代码一行没动但通信的实时性和吞吐量确实有明显变化。RMW 层也带来了 ROS2 的一个常见问题两个节点如果用了不同的 RMW 实现它们之间是发现不了对方的。这一点我在后面的章节里会详细展开这里先记住一个原则同一条通信链路上所有节点的 RMW 实现必须一致QoS 策略必须互相匹配否则通信就会静默失败或者直接报错。2. DDS 协议选型主流实现怎么选2.1 Fast DDS开箱即用的默认项Fast DDS 是 eProsima 主导开发的开源 DDS 实现也是 ROS2 从早期版本到现在的默认 RMW 实现。你安装的 ROS2 只要没特别配置过 RMW_IMPLEMENTATION系统默认走的就是 Fast DDS。Fast DDS 的优势首先体现在生态上。ROS2 官方测试得最多的是它教程、文档里示例代码对应的也是它。你搜 ros2 qos 的相关问题大概率会看到 Fast DDS 相关的报错和解决方案。这意味着什么问题都能找到参考遇到 bug 不会孤立无援。从性能上说Fast DDS 在单机多节点、局域网通信这些常见场景下的表现中规中矩。吞吐量的上限能吃满千兆网卡延迟通常在几毫秒量级。对于大多数 ROS2 应用包括导航、机械臂控制、多传感器数据聚合这个水平是完全够用的。Fast DDS 最明显的一个弱点是发现阶段的广播风暴。当网络里节点数量比较多的时候初始发现过程会产生大量 UDP 多播和单播报文如果节点不断重启这种发现流量还会反复出现。我在一个几十个节点的系统里实测过发现过程慢的时候要等十几秒而且会占用不少网络带宽。这个问题可以用 Fast DDS 的发现服务器Discovery Server机制来优化把发现流量集中到一个专门的服务器上可以显著减少网络负载和启动延迟。另外Fast DDS 对同一域内大量 topic 的处理效率会随着 topic 数量增加而下降。如果你构建的是一个超大型系统有几十个节点每个节点又发布十几个 topic那 Fast DDS 在主题匹配时的时间复杂度会明显升高。这时候就要考虑换 Cyclone DDS或者对 Fast DDS 做进一步的配置调优。2.2 Cyclone DDS性能敏感场景的备选方案Cyclone DDS 是 Eclipse 基金会旗下的开源 DDS 实现源自 OMG 的参考实现底层是用 C 语言写的核心代码抽取自 Real-Time InnovationsRTI早期捐献给 OMG 的参考代码后来逐渐发展成一套独立的、相当有性能竞争力的实现。Cyclone DDS 最大的卖点是性能。在相同硬件条件下Cyclone DDS 的消息发送延迟通常比默认情况下的 Fast DDS 更低、抖动也更小。这一点在做实时控制、高频传感器流比如激光雷达点云的时候特别明显。我做过一个点云传输对比测试同样是 32 线激光雷达、10Hz 的频率Fast DDS 在 QoS 设置为 Best Effort 时偶尔会有几百微秒到几毫秒的抖动而 Cyclone DDS 的抖动区间明显收敛得多。除了延迟Cyclone DDS 在内存占用上也更有优势。它的核心实现很精简不像 Fast DDS 那样有大量 C 模板和特性堆叠。对于树莓派、Jetson Nano 这类资源受限的嵌入式设备Cyclone DDS 能省下不少内存和 CPU 占用。切换到 Cyclone DDS 很简单只需要设置环境变量export RMW_IMPLEMENTATIONrmw_cyclonedds_cpp然后正常启动你的节点即可。注意切换之后不要忘记在所有节点上都设置相同的环境变量。我在实际项目里把控制类通信比如底盘速度指令、舵机位置指令切换到 Cyclone DDS 上因为这类通信对延迟和抖动最敏感而且数据量小、频率高正好发挥 Cyclone 的长处。而一些大数据量、对实时性要求不高的传感器数据比如相机图像、点云仍然留给 QoS 策略里的 Best Effort 模式去处理。2.3 RTI Connext 与 GurumDDS看看就好RTI Connext DDS 是老牌的商业 DDS 实现也是 DDS 领域的行业标杆之一。它的性能、稳定性、文档都是顶级的很多工业级系统都在用。ROS2 官方也有对应的 RMW 实现但是 Connext 是商业软件普通开发者需要申请许可证才能使用完整功能一般个人项目不会去选它。如果你是在做自动驾驶、医疗机器人这类有合规要求的商用产品那么 Connext 的成熟度和技术支持可能值这个钱但对 ROS2 教学、原型验证、实验室研发来说开源实现已经够用了。GurumDDS 是韩国一家公司开发的轻量级 DDS 实现ROS2 同样有对应的 RMW 实现。它的特点是代码量小、易裁剪适合做嵌入式移植。如果你在裸机或者 RTOS 上跑 ROS2比如通过 micro-ROSGurumDDS 可能是一个值得研究的选项。但对绝大多数跑在 Linux 上的机器人项目来说它的生态和资料比 Fast DDS 和 Cyclone DDS 要薄弱很多遇到问题排查起来比较费劲。做选型的时候我给一个比较务实的建议先在默认的 Fast DDS 上把功能跑通如果遇到性能瓶颈或者通信不稳定的问题再花一个下午切换到 Cyclone DDS 对比测试。大多数项目的性能瓶颈其实不在 DDS 本身而在 QoS 配置或者网络拓扑上。不要一开始就迷信某个 DDS 实现能解决所有问题。2.4 RMW 实现对比速查表实现协议开源/商业延迟表现内存占用生态完整度适用场景Fast DDSRTPS开源中等中等最高默认选择通用场景Cyclone DDSRTPS开源较低较低较高实时控制、资源受限设备RTI ConnextRTPS商业最低较高极高工业级、商业交付GurumDDSRTPS开源中等最低一般嵌入式、micro-ROS这张表不是严格意义上的性能横评同一实现不同配置差别也很大所以只能作为初选时的参考。实际选型的时候最好在自己的目标硬件上跑一轮基准测试用你真实的数据类型和频率来测这样得到的结果最有参考意义。3. QoS 策略通信稳定性的核心参数3.1 一对最容易踩坑的组合ReliabilityQoS 策略里面对通信行为影响最大的就是 Reliability。ROS2 里它有两个取值Reliable 和 Best Effort。Reliable 意味着每条消息都必须被订阅端确认收到如果发送过程中有消息丢失发送端会重传直到订阅端确认收到为止。这个语义和 TCP 类似适合指令下发、状态切换这种不能丢数据的通信。Best Effort 则相反数据发送之后就不管了丢了就丢了不会重传。它适合传感器数据流比如相机图像、激光雷达点云因为这类数据本身是以一定频率连续产生的偶尔丢一帧下一帧马上就跟上来重传反而会造成数据延迟越来越大。问题是在 ROS2 里发布端和订阅端的 Reliability 策略必须匹配否则通信建立不起来。匹配规则是发布端提供的 QoS 必须满足订阅端请求的 QoS如果发布端设置的是 Best Effort订阅端非要 Reliable那订阅端的请求永远无法被满足两端就会互相报 QoS incompatible 错误。举个例子你用 Realsense 相机驱动发布图像数据驱动默认把图像 topic 的 Reliability 设置成了 Best Effort然后你写了一个订阅节点没有显式设置 QoS用默认的 Reliable 去订阅图像。启动之后你会发现订阅端收不到任何数据终端里到处是 QoS 匹配失败的日志。我第一次遇到这个问题的时候排查了好久最后发现就是一行 QoS 设置的问题。正确的做法是在订阅端把 QoS 设置为 Best Effortfrom rclpy.qos import QoSProfile, ReliabilityPolicy qos QoSProfile( depth1, reliabilityReliabilityPolicy.BEST_EFFORT ) subscription self.create_subscription( Image, /camera/image_raw, self.callback, qos )在 C 里对应的写法rclcpp::QoS qos(1); qos.best_effort(); subscription node-create_subscriptionImage( /camera/image_raw, qos, callback);那个默认的 QoS 是 Reliable放在这里就是坑。所以在写订阅节点之前先ros2 topic info /topic --verbose看一下发布端的 QoS 设置再决定自己怎么配能省掉很多排查时间。3.2 Durability迟到的订阅者怎么办Durability 策略规定了当一个新的订阅者加入时发布端之前已经发送过的历史数据要不要补送给它。这里有两个主要取值Volatile 和 Transient Local。Volatile 是最常见的默认策略意思是发布端不保留历史数据新订阅者加入后只能收到它加入之后发布的新消息。这种情况适合大多数实时数据处理场景迟到的数据没有意义比如实时位姿、当前状态、传感器最新数据。Transient Local 则是发布端会在本地保留一部分历史数据有新的订阅者加入时会先把历史数据重放给它然后再继续接收新数据。这个策略非常适合那些本身没有常驻发布者、只有订阅者的场景尤其适合一些配置下发类的 topic。典型例子是地图服务。建图节点发布一张全局栅格地图地图生成后建图节点并不会一直以固定频率持续发布地图消息它可能只发一次就处于静默状态。如果你使用默认的 Volatile 策略导航模块启动时订阅地图 topic它可能什么数据都收不到因为发布端只在短暂的时间内发了一次地图消息订阅端启动的时候已经过了那一阵。这种情况下把发布端的 Durability 设置为 Transient Local地图消息就会被保留下来导航模块任何时候启动订阅都能立刻收到当前全局地图。ROS2 里设置 Durability 的 Python 示例from rclpy.qos import QoSProfile, DurabilityPolicy qos QoSProfile( depth1, durabilityDurabilityPolicy.TRANSIENT_LOCAL ) map_pub node.create_publisher(OccupancyGrid, /map, qos)发布端用 Transient Local订阅端只要请求的 QoS 里 durability 不高于这个等级也就是也请求 Transient Local 或 Volatile 均可就能正常工作。这个细节要注意订阅端的 durability 设置成 Volatile 也能收到 Transient Local 发布端重放的历史数据因为发布端提供的能力比订阅端要求的高是允许的。3.3 History 与 Depth队列深度的博弈History 策略规定了发送端和接收端各自保留多少历史数据。它有 Keep Last 和 Keep All 两种模式。Keep Last 只保留最近的 N 条消息N 就是 depth 参数Keep All 则保留所有消息直到消费者赶上进度这种模式对网络和内存的消耗非常大实际项目里很少直接用一般都会配合 depth 做限制所以 Keep Last 是我们最常用的配置。depth 的取值对通信行为影响很大。depth 越大允许积压的消息越多对突发流量的缓冲能力越强但同时会增加内存占用和延迟。depth 过小在消息产生速度超过消费速度的时候消息会被直接丢弃Best Effort 下即使是在 Reliable 模式下新消息也会把队列里最老的消息挤掉订阅端还没处理的旧消息就丢了。这里有一个常见的误区很多人以为把 depth 设置大一点就能避免丢消息其实增加 depth 只是给了系统更多的缓冲空间它不能解决消费端处理速度跟不上的根本问题。如果订阅回调函数里做的计算耗时很长比如做一次 SLAM 回环检测要几百毫秒那么这个回调处理完之前新来的消息已经堆积了很多depth 就算设成 100 也顶不住。真正处理速度瓶颈的办法是异步处理也就是在回调函数里只做轻量级的入队操作把耗时的计算放到另外一个线程里去执行。我在项目里处理相机图像时就采用了这个方案回调函数把图像帧的指针放进一个无锁队列专门的图像处理线程从队列里取数据做推理队列深度设置为 2。这样即使算法偶尔卡顿也不影响新的图像帧进入旧的没处理的帧则会被覆盖掉避免了内存无限增长。还有一个值得一提的细节不同 DDS 实现的 depth 语义在并发场景下略有差异。Fast DDS 里当 depth 满了且有新数据到达时会做丢弃最旧消息还是丢弃最新消息的处理我记得不同版本行为并不完全一致。所以不要靠深队列去解决通信问题烦请从源头上优化消费逻辑。3.4 其他常用 QoS 策略除了上面三组核心策略ROS2 的 QoS 系统还包括不少其他参数虽然用的频率没那么高但特定场景下会非常有用。Deadline 策略可以用来监控两个消息之间的最大间隔。发布端声明自己在指定的时间间隔内至少发布一条消息订阅端也可以声明自己能接受的最大发布间隔。如果发布端在 Deadline 时间里没有发布新消息系统会触发 deadline 相关的事件回调这在做远程状态监控和心跳检测时非常有用。比如底盘控制器节点可以发布一个状态消息设置 deadline 为 100ms主控节点订阅这个状态如果超过 100ms 没有收到消息就认为底盘通信异常可以触发急停逻辑。Lifespan 策略为消息设置了最大存活时间。发布端可以给消息带上一个过期时间戳DDS 实现会在消息层级上自动判断过期后订阅端不会再收到或者会在收到时丢弃。这个策略适合对实时性要求极高、发送过时的信息反而会造成误导的场景。Partition 策略可以将同一个 topic 划分为不同的逻辑分区只有同一个分区的发布者才收得到对应的消息。这个策略在做多机器人的时候基本都用不上因为不同机器人通常直接跑不同的域 IDdomain_id比 Partition 简单直接得多。下面是日常开发中最常用到的 QoS 参数速查表参数作用建议取值典型用途Reliability消息是否可靠送达Reliable / Best Effort指令用 Reliable传感器流用 Best EffortDurability是否保留历史数据Volatile / Transient Local地图、参数配置用 Transient LocalHistory历史消息保留策略Keep Last / Keep All默认 Keep LastDepth队列深度1~100 根据实际压力调平衡内存和缓冲Deadline消息最大间隔根据控制频率设定通信异常监控、心跳Lifespan消息过期时间不超过控制周期实时状态类通信4. 实操在 ROS2 里正确配置 QoS 与 RMW4.1 几种代码设置 QoS 的姿势ROS2 的客户端库提供了丰富的 QoS 设置方式从最简到最细都有对应 API。最常用的方式是直接构造 QoSProfile 对象也可以直接用 rclcpp 里内置的 QoS 预设常量。C 中rclcpp::QoS 的构造函数接收一个整数表示 depth然后可以链式调用策略设置方法rclcpp::QoS qos_profile(10); qos_profile.reliable(); qos_profile.transient_local(); qos_profile.keep_last(10);如果只想快速改一个策略其他保持默认也可以直接传预设对象rclcpp::QoS qos_profile_2 rclcpp::SensorDataQoS(); // 预置了 best_effort这个 SensorDataQoS 预置方案非常实用它包含 depth5、Best Effort、Volatile、Keep Last 等策略组合做传感器订阅时直接拿来用省得每次都要重新配。Python 里通过 QoSProfile 对象来构造from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy, HistoryPolicy qos_profile QoSProfile( historyHistoryPolicy.KEEP_LAST, depth10, reliabilityReliabilityPolicy.RELIABLE, durabilityDurabilityPolicy.TRANSIENT_LOCAL, ) publisher node.create_publisher(String, chatter, qos_profile)注意 Python 里的 QoSProfile 构造时还需要 history 参数不然会使用默认的 KEEP_LAST但 depth 如果没传默认是 10。这个还好但是如果只传 depth 不传 reliability系统不会帮你自动判断浅显的例子就是用默认 reliable 去订阅相机 topic 就掉坑里了。4.2 RMW 的环境变量与动态切换ROS2 的 RMW 实现选择和环境变量绑定主要的环境变量是 RMW_IMPLEMENTATION 和 ROS_DOMAIN_ID。RMW_IMPLEMENTATION 决定用哪个 DDS 实现ROS_DOMAIN_ID 决定使用哪个 DDS Domain。设置 RMW_IMPLEMENTATION 时要注意装好 ROS2 不一定就会出现你需要的 RMW 实现。Ubuntu 里安装 ros-humble-rmw-cyclonedds-cpp 之后你才能使用 Cyclone DDS否则设置了环境变量也没用。sudo apt install ros-humble-rmw-cyclonedds-cpp export RMW_IMPLEMENTATIONrmw_cyclonedds_cppROS_DOMAIN_ID 的配置稍微复杂一些它默认取值 0。如果你在同一台电脑上跑多套独立的 ROS2 系统比如一个给导航用、一个给机械臂控制用可以通过设置不同 domain id 来让它们互不干扰。域 ID 的取值范围是 0~101某些实现更高每个域的通信完全隔离。我实际测试过把 domain id 设置成不同的值之后两个系统之间的节点完全发现不了彼此topic 是隔离的。这在同一台机器上调试多家供应商提供的 ROS2 驱动时很有用避免 node name 和 topic name 互相冲突。有一个完整的启动脚本可以参考export RMW_IMPLEMENTATIONrmw_cyclonedds_cpp export ROS_DOMAIN_ID2 source /opt/ros/humble/setup.bash ros2 launch my_robot_bringup robot.launch.py同一个系统里所有节点的 RMW_IMPLEMENTATION 和 ROS_DOMAIN_ID 必须一致否则节点之间是无法发现的。在终端里跑任何 ROS2 命令之前都要先检查这两个环境变量。4.3 命令行查看 QoS 和通信状态调试 QoS 问题的时候ros2 CLI 提供了一些命令能帮我们快速看到通信状态。ros2 topic info加上 --verbose 参数可以列出某个 topic 的发布者和订阅者以及它们各自使用的 QoS。ros2 topic info /camera/image_raw --verbose输出里会显示 Publisher 和 Subscription 的 QoS 配置包含 Reliability、Durability、Depth 等。只要对比发布端和订阅端的 QoS就能快速判断是不是 QoS 不匹配导致的通信失败。ros2 doctor是一个整系统健康检查工具它可以检查当前环境的 RMW 实现、网络配置、节点发现状态等。如果系统里节点之间互相发现不了执行ros2 doctor --report可以导出完整的诊断信息这个信息在论坛上求助时非常有用相当于一次把环境快照发给了对方。想要实时看 topic 通信质量还有一个常用的绕路方法用ros2 topic hz统计消息频率。ros2 topic hz /odom如果频率远低于发布端设定的目标频率说明存在丢包或者消息调度问题。再用ros2 topic bw看带宽占用情况结合这两个命令的参数表现就能大概判断是 QoS 的问题还是网络带宽的问题。5. 常见问题与排查技巧实录5.1 QoS incompatible 报错现象订阅端节点启动后终端刷出new publisher discovered on topic /xxx, offering incompatible QoS之类的日志数据收不到。原因发布端和订阅端的某些 QoS 策略不匹配。最常见的是 Reliability 不匹配发布端是 Best Effort订阅端是 Reliable其次是 Durability 不匹配发布端是 Volatile订阅端请求 Transient Local这在普通情况下极少见但发布端如果配置的是 Transient Local 而订阅端需要更强的持久性也可能会报错。排查方法查看发布端实际 QoSros2 topic info /topic --verbose。检查订阅端代码里的 QoSProfile逐一比对 Reliability、Durability、Depth。优先满足发布端的设置让订阅端向发布端靠近。这里有一个基本规则只要发布端能够满足订阅端的请求通信就能建立。也就是说发布端的 QoS 等级需要不低于订阅端请求的等级。具体到策略上来讲Reliability 里 Reliable 是较高的保证Best Effort 是较低的保证Durability 里 Transient Local 是较高的Volatile 是较低的。5.2 节点互相发现不了但 QoS 看起来没问题现象两个节点在同一个域里topic 和 QoS 完全匹配但订阅端就是收不到数据rostopic echo 也是空的。原因大概率是 RMW 实现不一致或者网络发现被隔断了。第一优先级检查echo $RMW_IMPLEMENTATION不同终端如果值不一样节点之间当然互相看不见。其次是防火墙DDS 使用 UDP 多播做发现如果系统防火墙屏蔽了多播报文和 UDP 端口节点之间就无法互相发现。有一个小技巧同一台机器上用 sys 命令打印当前 DDS 相关环境env | grep -E RMW|ROS|CYCLONEDDS|FASTRTPS如果 RMW、ROS 相关变量都有但节点还是发现不了可以检查防火墙。Ubuntu 下 ufw 如果开启可以临时先关掉试试是否是防火墙的锅sudo ufw disable如果关闭之后问题解决那就把 DDS 需要的端口加白名单。不同 DDS 实现的默认端口不同Fast DDS 默认使用的多播地址是 239.255.0.1Cyclone DDS 默认多播地址也类似端口分布在 7400~7500 和 7400~7600 区间内。这部分端口和地址在白名单里需要仔细确认不同版本可能有差异。5.3 传感器 topic 收不到数据但命令 cmd_vel 可以收到现象cmd_vel 指令能正常收发导航模块也能正常工作但 LaserScan、PointCloud2 这些传感器数据在订阅端收不到。原因这基本是 Reliability 策略的问题。很多传感器驱动比如 Livox、Realsense 的 ROS2 驱动默认把传感器 data topic 设置为 Best Effort因为数据量大、帧率高做可靠传输会拖慢整个链路。而你的订阅节点没有显式设置 QoS用的是默认 Reliable所以通信匹配失败。解决订阅传感器 topic 的时候显式设置 QoS按照 4.1 节的方式设置 best_effort或者直接使用 rclcpp::SensorDataQoS() 预设。千万不要为了做实验去修改传感器驱动的 QoS因为那是官方测试过的稳定配置。5.4 多机通信失联现象A 电脑上的节点发布 topicB 电脑上的节点订阅但两边都启动正常却没数据通信。原因多机通信比单机复杂常见原因有两个机器的 domain id 不一致。两个机器的 ROS_DOMAIN_ID 都设置成 0默认但因为网络隔离或防火墙阻止了广播DDS 的多播发现报文发不出去。两个机器的 hostname 无法互相解析DDS 节点发现时需要知道对端的 IP而 ROS2 里默认使用机器名来宣告地址信息。第三点是最隐蔽的。DDS 的发现报文里包含参与者的地址信息而参与者地址通常用 hostname 表示。如果 A 机器叫 robot-aB 机器叫 robot-b那么 A 发给 B 的发现报文里会写 我是 robot-aB 收到后尝试解析 robot-a 的主机名时解析失败就会认为这个参与者无法到达于是通信失败。解决方式是在两台机器的 /etc/hosts 里加上对方的 IP 和主机名映射让 DDS 的地址解析能够成功192.168.1.10 robot-a 192.168.1.11 robot-b还有一种更直接的方式是让 DDS 的所有通信都走单播而不是多播并指定网卡接口。Fast DDS 里可以通过 XML 配置指定 network interfaceCyclone DDS 也提供了类似的配置项。在配置 XML 里明确指定网卡一般能解决多机发现异常的问题。5.5 通信性能不好延迟高、吞吐低现象大数据量 topic图像、点云的传输延迟很高订阅端处理速度跟不上CPU 占用居高不下。原因Reliability 设置成了 Reliable大数据量下重传频繁。depth 太大数据积压导致内存频繁分配和释放。序列化及反序列化的 CPU 开销。ROS2 默认的 CDR 序列化实现有较大的计算开销在处理点云时尤其明显。使用了不合适的共享内存传输或者禁用共享内存选项导致走了 TCP/UDP 模式其实 ROS2 默认是 UDP。优化方向点云、图像类 topic 的订阅端都用 Best Effort。检查是否有共享内存传输可用。较新的 ROS2 支持 intra-process 和 shared memory 传输对单机多节点场景有很大提升。大数据量 topic 尽量使用数据压缩比如点云先做降采样再发布这会减少网络带宽占用但会增加 CPU 压缩开销需要根据实际情况平衡。6. 写在最后我踩过的一些坑和一个小技巧做 ROS2 这几个月我在 QoS 和 DDS 上踩过的坑比 ROS1 时代踩的所有通信坑加起来还多。最开始遇到 QoS incompatible 的时候我以为是代码写错了反复检查发布订阅逻辑完全没有头绪。后来才知道是默认 QoS 和传感器驱动的 Best Effort 冲突改一行就通了。从那以后我养成一个习惯任何新接手的 ROS2 项目第一件事就是用ros2 topic info查看所有关键 topic 的 QoS 设置把发布端的 QoS 记录下来再同步到订阅端代码里。还有一个细节如果你的系统里既有 ROS1 的节点又有 ROS2 的节点建议用 ros1_bridge 做桥接不要指望它们能直接通信两个时代的通信中间件完全不是一回事。桥接那边也有类似的 QoS 匹配问题ROS1 侧的 topic 只会被桥接成一组合理的默认 QoS如果你的 ROS2 订阅端要求 high 等级的 QoS还是会对不上。最后再分享一个小技巧如果调试时怀疑某个 topic 因为 QoS 问题收不到数据先用ros2 topic echo手动订阅一下这个 topic。如果 CLI 能收到说明发布端和 QoS 配置本身没问题问题在你自己代码的订阅端如果 CLI 也收不到那就去查发布端和网络。用绕开自己代码的方式缩小问题范围速度会快很多。这套思路看起来简单真的能帮你少走几天弯路。
返回列表