我们在《ROS2概述和基于RK3588的环境搭建》中对ROS和ROS2做了对比,其中最多的变化就是DDS。我们在前面文章中介绍的话题、服务、动作,他们底层通信的具体实现过程,都是靠DDS来完成的,它相当于是ROS机器人系统中的神经网络。
一、通信模型
DDS的核心是通信,能够实现通信的模型和软件框架非常多,这里我们列出常用的四种模型。

1.1 点对点模型
许多客户端连接到一个服务端,每次通信时,通信双方必须建立一条连接,当通信节点增多时,连接数也会增多。
而且每个客户端都需要知道服务器的具体地址和所提供的服务,一旦服务器地址发生变化,所有客户端都会受到影响。
1.2 Broker模型
针对点对点模型进行了优化,由Broker集中处理所有人的请求,并进一步找到真正能响应该服务的角色,这样客户端就不用关心服务器的具体地址了。
不过问题也很明显,Broker作为核心,它的处理速度会影响所有节点的效率,当系统规模增长到一定程度,Broker就会成为整个系统的性能瓶颈。更麻烦是,如果Broker发生异常,可能导致整个系统都无法正常运转,之前的ROS1系统,使用的就是类似这样的架构。
1.3 广播模型
所有节点都可以在通道上广播消息,并且节点都可以收到消息。这个模型解决了服务器地址的问题,而且通信双方也不用单独建立连接,但是广播通道上的消息太多了,所有节点都必须关心每条消息,其实很多是和自己没有关系的。
1.4 以数据为中心的DDS模型
这种模型与广播模型有些类似,所有节点都可以在DataBus上发布和订阅消息。但它的先进之处在于,通信中包含了很多并行的通路,每个节点可以只关心自己感兴趣的消息,忽略不感兴趣的消息,有点像是一个旋转火锅,各种好吃的都在这个DataBus传送,我们只需要拿自己想吃的就行,其他的和我们没有关系。
以数据为中心更本质的体现是数据域(Domain)概念。一个DDS Domain定义了一个独立的虚拟网络空间,只有拥有相同Domain ID的参与者(对应ROS2节点)才能相互发现和通信。这就像一栋办公楼,不同的Domain ID是不同的楼层,同楼层的公司(节点)可以自由交流,不同楼层则完全隔离,互不干扰。
二、DDS
DDS并不是一个新的通信方式,在ROS2之前,DDS已经广泛应用在很多领域,比如航空,国防,交通,医疗,能源等。

比如在自动驾驶领域,通常会存在感知,预测,决策和定位等模块,这些模块都需要非常高速和频繁地交换数据。借助DDS,可以很好地满足它们的通信需求。
2.1 DDS定义
DDS的全称是Data Distribution Service,也就是数据分发服务,2004年由对象管理组织OMG发布和维护,是一套专门为实时系统设计的数据分发/订阅标准,最早应用于美国海军, 解决舰船复杂网络环境中大量软件升级的兼容性问题,现在已经成为强制标准。
DDS强调以数据为中心,可以提供丰富的服务质量策略,以保障数据进行实时、高效、灵活地分发,可满足各种分布式实时通信应用需求。
这里也提一下对象管理组织OMG,成立于1989年,它的使命是开发技术标准,为数以千计的垂直行业提供真实的价值,比如我们可能听说过的统一建模语言SYSML和UML,还有中间件标准CORBA等,当然还有DDS。
2.2 在ROS2中的应用
DDS在ROS2系统中的位置至关重要,所有上层建设都建立在DDS之上。在这个ROS2的架构图中,蓝色和红色部分就是DDS。

DDS其实是物联网中广泛应用的一种通信协议,类似于我们常听说的5G通信一样,DDS是一个国际标准,能够实现该标准的软件系统并不是唯一的,所以我们可以选择多个厂家提供的DDS系统,比如这里的OpenSplice、FastRTPS,还有更多厂家提供的,每一家的性能不同,适用的场景也不同。
不过这就带来一个问题,每个DDS厂家的软件接口肯定是不一样的,如果我们按照某一家的接口写完了程序,想要切换其他厂家的DDS,不是要重新写代码么?这当然不符合ROS提高软件复用率的目标。
为了解决这个问题,ROS2设计了一个ROS Middleware,简称RMW,也就是指定一个标准的接口,比如如何发数据,如何收数据,数据的各种属性如何配置,都定义好了,如果厂家想要接入ROS社区,就得按照这个标准写一个适配的接口,把自家的DDS给移植过来,这样就把问题交给了最熟悉自家DDS的厂商。对于我们这些用户来讲,某一个DDS用的不爽,只要安装另一个,然后做一个简单的配置,程序一行的都不用改,轻松更换底层的通信系统。
举一个例子,比如我们在产品开发时,可以先用开源版本的DDS满足基本需求,部署交付的产品时,再更换为商业版本更稳定的DDS,这样可以减少开发成本。

总之,DDS的加入,让ROS2系统更加稳定,也更加灵活,当然复杂度也会高一些。这样,我们不用再纠结ROS的通信系统是否稳定、该如何优化等问题,更多精力都可以放在其他三个部分,专注优化我们的机器人应用功能。
2.3 服务质量QoS
DDS为ROS的通信系统提供提供了哪些特性呢?我们通过这个通信模型图来看下;

DDS中的基本结构是Domain,Domain将各个应用程序绑定在一起进行通信,回忆下之前我们配置NanoPC-T6开发板和ubuntu虚拟机通信的时候,配置的那个Domain ID,就是对全局数据空间的分组定义,只有处于同一个Domain小组中的节点才能互相通信,这样可以避免无用数据占用的资源。
DDS中另外一个重要特性就是质量服务策略QoS,QoS是一种网络传输策略,应用程序指定所需要的网络传输质量行为,QoS服务实现这种行为要求,尽可能地满足客户对通信质量的需求,可以理解为数据提供者和接收者之间的合约。
具体会有哪些策略?比如:
DEADLINE:表示通信数据必须要在每次截止时间内完成一次通信;HISTORY:表示针对历史数据的一个缓存大小;RELIABILITY:表示数据通信的模式;- 配置成
BEST_EFFORT,就是尽力传输模式,网络情况不好的时候,也要保证数据流畅,此时可能会导致数据丢失; - 配置成
RELIABLE,就是可信赖模式,可以在通信中尽量保证图像的完整性,我们可以根据应用功能场景选择合适的通信模式;
- 配置成
DURABILITY:可以配置针对晚加入的节点,也保证有一定的历史数据发送过去,可以让新节点快速适应系统。
2.3.1 机器人案例
举一个机器人的例子便于大家理解;

2.3.1 无人机案例
比如我们遥控一个无人机航拍,如果网络情况不好的话,遥控器向无人机发送运动指令的过程,可以用reliable通信模式,保证每一个命令都可以顺利发送给无人机,但是可能会有一些延时,无人机传输图像的过程可以用best effort模式,保证视频的流畅性,但是可能会有掉帧。

如果此时出现一个黑客黑入我们的网络,也没有关系,我们可以给ROS2的通信数据进行加密,黑客也没有办法直接控制无人机。
DDS的加入,让ROS2的通信系统焕然一新,多众多样的通信配置,可以更好的满足不同场景下的机器人应用。
三、DDS案例
3.1 在命令行中配置DDS
我们先来试一试在命令行中配置DDS的参数。
3.1.1 发布者
启动第一个终端,我们使用best_effort创建一个发布者节点,循环发布任意数据;
shell
pi@NanoPC-T6:~$ ros2 topic pub /chatter std_msgs/msg/Int32 "data: 42" --qos-reliability best_effort
publisher: beginning loop
publishing #1: std_msgs.msg.Int32(data=42)
publishing #2: std_msgs.msg.Int32(data=42)
publishing #3: std_msgs.msg.Int32(data=42)
publishing #4: std_msgs.msg.Int32(data=42)
3.1.2 订阅者
在另外一个终端中,如果我们使用reliable模型订阅同一话题,则无法实现数据通信;
shell
pi@NanoPC-T6:~$ ros2 topic echo /chatter --qos-reliability reliable
[WARN] [1765976222.618569414] [_ros2cli_2137]: New publisher discovered on topic '/chatter', offering incompatible QoS. No messages will be received from it. Last incompatible policy: RELIABILITY
修改为同样的best_effort,才能实现数据传输;
shell
Cpi@NanoPC-T6:~ros2 topic echo /chatter --qos-reliability best_effortrt
data: 42
---
data: 42
---
data: 42
---
data: 42
---
data: 42
---
data: 42
---
data: 42
---
3.1.3 查看QoS策略
如何去查看ROS2系统中每一个发布者或者订阅者的QoS策略呢,在topic命令后边跟一个--verbose参数就行了;
shell
pi@NanoPC-T6:~$ ros2 topic info /chatter --verbose
Type: std_msgs/msg/Int32
Publisher count: 1
Node name: _ros2cli_2064
Node namespace: /
Topic type: std_msgs/msg/Int32
Endpoint type: PUBLISHER
GID: 01.0f.50.2b.10.08.90.1b.00.00.00.00.00.00.05.03.00.00.00.00.00.00.00.00
QoS profile:
Reliability: BEST_EFFORT
History (Depth): UNKNOWN
Durability: TRANSIENT_LOCAL
Lifespan: Infinite
Deadline: Infinite
Liveliness: AUTOMATIC
Liveliness lease duration: Infinite
Subscription count: 1
Node name: _ros2cli_2200
Node namespace: /
Topic type: std_msgs/msg/Int32
Endpoint type: SUBSCRIPTION
GID: 01.0f.50.2b.98.08.ab.93.00.00.00.00.00.00.05.04.00.00.00.00.00.00.00.00
QoS profile:
Reliability: BEST_EFFORT
History (Depth): UNKNOWN
Durability: VOLATILE
Lifespan: Infinite
Deadline: Infinite
Liveliness: AUTOMATIC
Liveliness lease duration: Infinite
3.2 DDS编程示例
接下来,我们尝试在代码中配置DDS,以之前Hello World话题通信为例。

我们首先创建my_learning_qos 的Python版本的功能包;
shell
pi@NanoPC-T6:~/dev_ws$ cd src
pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_python my_learning_qos
3.2.1 发布者
在my_learning_qos文件夹下创建qos_helloworld_pub.py;
python
"""
ROS2 QoS示例-发布"Hello World"话题
@author: zy
@since : 2025/12/17
"""
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # 字符串消息类型
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy # ROS2 QoS类
"""
创建一个发布者节点
"""
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
qos_profile = QoSProfile( # 创建一个QoS原则
# reliability=QoSReliabilityPolicy.BEST_EFFORT,
reliability=QoSReliabilityPolicy.RELIABLE,
history=QoSHistoryPolicy.KEEP_LAST,
depth=1
)
self.pub = self.create_publisher(String, "chatter", qos_profile) # 创建发布者对象(消息类型、话题名、QoS原则)
self.timer = self.create_timer(0.5, self.timer_callback) # 创建一个定时器(单位为秒的周期,定时执行的回调函数)
def timer_callback(self): # 创建定时器周期执行的回调函数
msg = String() # 创建一个String类型的消息对象
msg.data = 'Hello World' # 填充消息对象中的消息数据
self.pub.publish(msg) # 发布话题消息
self.get_logger().info('Publishing: "%s"' % msg.data)# 输出日志信息,提示已经完成话题发布
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = PublisherNode("qos_helloworld_pub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
python
entry_points={
'console_scripts': [
'qos_helloworld_pub = my_learning_qos.qos_helloworld_pub:main',
],
},
3.2.2 订阅者
在my_learning_qos文件夹下创建qos_helloworld_sub.py;
python
"""
ROS2 QoS示例-订阅"Hello World"话题消息
@author: zy
@since : 2025/12/17
"""
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # ROS2标准定义的String消息
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy # ROS2 QoS类
"""
创建一个订阅者节点
"""
class SubscriberNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
qos_profile = QoSProfile( # 创建一个QoS原则
# reliability=QoSReliabilityPolicy.BEST_EFFORT,
reliability=QoSReliabilityPolicy.RELIABLE,
history=QoSHistoryPolicy.KEEP_LAST,
depth=1
)
self.sub = self.create_subscription(
String, "chatter", self.listener_callback, qos_profile) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、QoS原则)
def listener_callback(self, msg): # 创建回调函数,执行收到话题消息后对数据的处理
self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = SubscriberNode("qos_helloworld_sub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
python
entry_points={
'console_scripts': [
'qos_helloworld_pub = my_learning_qos.qos_helloworld_pub:main',
'qos_helloworld_sub = my_learning_qos.qos_helloworld_sub:main',
],
},
DDS本身是一个非常复杂的系统,ROS2使用的也只是冰山一角,这一节我们主要认识了DDS,更多使用方法和相关内容,可以参考文章最后的链接进行学习。
3.2.3 编译运行
编译程序:
shell
pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_qos
启动第一个终端,运行发布者节点;
shell
pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_qos qos_helloworld_pub
[INFO] [1765977701.017109522] [qos_helloworld_pub]: Publishing: "Hello World"
[INFO] [1765977701.481142094] [qos_helloworld_pub]: Publishing: "Hello World"
[INFO] [1765977701.981325128] [qos_helloworld_pub]: Publishing: "Hello World"
[INFO] [1765977702.481208424] [qos_helloworld_pub]: Publishing: "Hello World"
[INFO] [1765977702.980783816] [qos_helloworld_pub]: Publishing: "Hello World"
[INFO] [1765977703.481164427] [qos_helloworld_pub]: Publishing: "Hello World"
启动第二个终端,运行订阅者节点;
shell
pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_qos qos_helloworld_sub
[INFO] [1765977718.509490470] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977718.981377865] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977719.483689245] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977719.982267400] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977720.483497158] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977720.983902698] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977721.484018419] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977721.981400832] [qos_helloworld_sub]: I heard: "Hello World"
[INFO] [1765977722.483140196] [qos_helloworld_sub]: I heard: "Hello World"
看效果确实差不多,不过底层通信机理上可是有所不同的。
[1] 古月居ROS2入门教程学习笔记
[2] ROS on DDS
[3] https://docs.ros.org/en/humble/Concepts/About-Different-Middleware-Vendors.html
[4] https://docs.ros.org/en/humble/How-To-Guides/Working-with-multiple-RMW-implementations.html