本文简要阐述ROS2相对与ROS的差别,具体细节可见后续章节,两者的大致可从以下角度进行对比分析:

上图来源ROS2官方在期刊Science Robotics发表的论文:《Robot Operating System 2: Design, architecture, and uses in the wild》
零、操作系统
ROS只能运行在Linux操作系统上。
ROS2可以运行在Linux、Windows和MacOS操作系统上,甚至可以运行在嵌入式实时操作系统上,具体可以见博客《微控制器上用于 ROS2 的 micro-ROS》。
一、系统架构
ROS和ROS2的系统架构可以划分为三层,分别是应用层,中间层,系统层,其中中间层是ROS和ROS2的主要差别!

这些差别体现在以下四个方面:
进程管理:在ROS1中,需要开启中央节点管理器Master,统一管理所有节点。如果Master节点出现故障,将严重影响ROS系统功能。在ROS2中,系统引入节点自发现机制,可有效提高系统鲁棒性。
进程间通信:ROS1基于TCP和UDP协议自己开发了TCPROS和UDPROS协议,而在ROS2中,通信协议更换成了更加复杂但也更加完善的DDS系统。
进程内通信:如果是在进程内需要进行大量数据的通信,ROS1 和ROS2都提供了基于共享内存的通信方法,分别是Nodelet和 Intra-process模块。
跨平台:最下边是系统层,也就是可以将ROS安装在哪些操作系统上,ROS1主要安装在Linux上,ROS2可跨平台,如Linux、windows、MacOS、RTOS。
1.1 ROS2 Middleware Layer(中间层)组成结构
将ROS2的Middleware Layer(中间层)放大,可以观察到更多细节!
ROS2的Middleware Layer(中间层)可分为三层,从下往上分别是DDS Implementation层、Abstract DDS层和ROS2 Client层。

DDS Implementation层:该层是DDS实现层,由各个厂商提供相应的DDS软件产品。该层的目的是为了让用户可以根据自己实际需求选择不同厂商,如开源的Fast DDS,或商业的RTI Connext DDS。
Abstract DDS层:称为RMW(ROS MiddleWare Interface)层。该层是相对底层的接⼝层,由C语⾔实现,直接和DDS交互,并向ROS2 Client层提供API接口。该层的目的是让用户程序可以在不同供厂商的DDS之间进行无感移植。
ROS2 Client层:称为RCL(ROS Cliend Libraries)层。该层包含ROS2核心概念如Node、Action等的具体实现,以及不同编程语言的API接口,如C++、Python和Java等API接口。在ROS中,不同编程语言的ROS核心库及API是独立实现的;而在ROS2中,不同编程语言的RCL库是一致的,都是C实现的,而编程语言API接口是独立的,从而保证了不同编程语言下程序运行的一致性。
1.2 ROS2 Middleware Layer(中间层)API接口
ROS2有以下三类主要接口:
ROS2中间件接口(RMW API)
ROS2客户端库接口(RCL API)
ROS2用户编程接口(rclcpp、pclpy、rcljava API)

中间件接口RMW API:是ROS 2 RCL库和底层DDS中间件实现之间的接口。通过该RMW API接口可以调用底层DDS实现订阅/发布话题、请求-服务、节点发现、图事件等功能。
客户端库接口RCL API:用于实现客户端库,且不直接接触DDS中间件实现,而是通过ROS中间件接口(RMW API)抽象来间接接触中间件实现的。通过该API可以调用ROS2的各类资源,如创建节点、参数设置等ROS2的核心功能。
ROS2用户编程接口(rclcpp、pclpy、rcljava API):普通的ROS使用者将会使用用户编程API来实现其代码,如这些用户编程接口的实现使用了rcl接口,可提供了对ROS状态图及状态图事件的访问功能。
ros_to_dds接口:在上图中,还有一个标记为ros_to_dds的方框,该方框的目的是表示一种可能的软件包,这种软件包允许使用者使用ROS等效项访问DDS供应商的特定对象和设置。为避免直接使用DDS底层API,ROS2官方要求这些DDS供应商提供可访问底层DDS对象的API接口,即ros_to_dds API接口。
RMW接口目标是把ROS用户空间中的代码与所使用的DDS中间件完全隔离开来,从而把不断改变的DDS供应商甚至中间件技术对用户代码的影响降至最低。但是某些情况下,需要直接深入到DDS实现并手动调整其设置,虽然者可能会带来兼容性等不良后果。
通过检查软件包的依赖关系来查看是否使用了这些ros_to_dds软件包之一,还可以很容易地看出哪些代码可能侵犯了供应商的可移植性。
二、通信模型
2.1 ROS通信模型
ROS的通讯系统基于TCPROS/UDPROS,强依赖于master节点的处理。ROS默认采用TCP进行通信,但是在实际的WIFI网络容易出现连接中断且无法重新建立连接。ROS wiki中官方提到,TCPROS更适合有线网连接的网络,而UDPROS更适合wifi等网络不可靠的无线网络。

ROS基于TCP通讯过程:
0). Advertise:Talker 注册(Advertise )话题名称和消息类型
1). Subscribe:Listener订阅(Subscribe)话题名称
2). Match:ROS Master 基于XML/RPC将相同话题的Talker和Listener进行匹配
3). Listener 发送链接请求(TCP的三次握手协议)
4). Talker 确认请求(TCP的三次握手协议)
5). Talker和Listener建立TCP连接(TCP的三次握手协议)
6). Talker 给 Listener 发数据
若想使用UDP发送数据,Publisher必须是用roscpp写的,因为rospy不支持udp连接,然后在定义Subscriber时添加 ros::TransportHints参数,指定连接方式为unreliable,如下:
ros::Subscriber chatter_sub = private_nh.subscribe("/chatter", 10, call_back, ros::TransportHints().unreliable().maxDatagramSize(1000));
2.2 ROS2通信模型
ROS 2的通讯系统是基于DDS,进而取消了master,同时在ROS2内部提供了DDS的抽象层实现,有了这个抽象层,用户就可以不去关注底层的DDS使用了哪个商家的API。

如上图所示, ROS通讯模型由七个实体构成:Domain,Participant,Publisher,Subscriber,DataWriter,DataReader和Topic。根据服务质量策略(QoS Policy)执行进程之间的每个数据传输。
Domain: 它定义了一个单独的通信平面。几个域可以同时独立地共存。一个域包含任意数量的 Participant,即能够发送和接收数据的元素。
Participant: 用于跟踪其他实体和服务入口点的容器。在DDS中,所有应用程序在Domain内相互通信,从而促进隔离和通信优化。
Publisher: Publisher是负责数据发布的对象。管理一个或多个DataWriters,Publisher将数据发送到一个或多个主题。
Subscriber: Subscriber负责接收已发布的数据并使数据可用。Subscriber代表一个或多个DataReader句柄。根据Subscriber,Participant可以接收和发送不同指定类型的数据。
DataWriter: DataWriter是Participant必须使用的对象,通过Publisher发布数据, DataWriter发布给定类型的数据。
DataReader: DataReader是附加到订阅服务器的对象。使用DataReader,Participant可以接收和访问与DataWriter的数据类型相匹配的数据。
Topic: Topic用于标识DataWriter和DataReader之间的每个数据对象。每个主题由名称和数据类型定义。
DDS全局数据空间可以理解为:由存在于不同物理位置上的HistoryCache构造的全局数据空间逻辑上统一,物理上分布式而组成。

2.3 ROS1和ROS2通信性能对比
这里的所有对比数据和图片来自于论文《Exploring the Performance of ROS2》,中文翻译见博客《【ROS2】系列(五)——Exploring the Performance of ROS2》。
这篇论文中,作者针对ROS1和ROS2在延时、吞吐量、线程数、内存消耗等几个方面的性能进行了量化对比,数据量从256byte到4M,实验条件覆盖了ROS分布式的多种使用场景,具有一定的参考价值,也是目前对ROS2性能方面量化分析的唯一论文,不过需要注意的是论文使用的ROS2是Alpha版本。
2.3.1 测试场景和方法

2.3.2 可靠性

2.3.3 延迟

2.3.4 吞吐量

2.3.5 线程数

2.3.6 内存消耗

2.3.7 总结
- 根据作者的对比数据,可以发现ROS2的性能在不同数据量的场景下,表现呈指数级变化,应该是大数据在底层传输的一些处理时间导致的(可以通过引入ZeroMQ来解决,比如地平线的TogetherROS)。
- 不同厂商的DDS产品性能表现,相差巨大,应该根据不同的应用场景选用最佳的DDS产品。
- QoS提供了多种配置选项,同样需要根据不同的应用场景,选择最适合自己的配置。
- ROS1会丢失初始数据,这一点我只前还没有发现,ROS2看上去在这一点上好了不少。
- ROS2目前的整体性能(论文中使用的Alpha版本(2017))并不如ROS1,毕竟还处于开发阶段,而且对DDS的特性支持有限,但是毕竟潜力在那里。
2.4 SROS(Security ROS)
安全是任何现代商业机器人SDK的一个重要元素。ROS2不仅依赖于DDS安全标准,而且还提供了一套额外的工具SROS2,以简化安全基础设施的管理。DDS安全中有三个主要概念:身份验证、访问控制和加密。
身份验证:这建立了网络中消息或参与者的身份。ROS 2使用数字签名进行身份验证,称为公钥密码学。SROS2包含用于生成和存储这些数字签名的命令行实用程序。
访问控制:这允许将细粒度的策略应用到经过身份验证的网络参与者。它允许只有批准的参与者才能被发现,并通过预先批准的网络接口进行通信。SROS2有用于生成这些配置的命令行工具。
加密:确保第三方无法窃听或重放数据到网络中。加密使用高级加密标准伽罗瓦/计数器模式(AES-GCM)对称密钥密码学来执行。密钥材料来源于作为身份验证的一部分而获得的共享密钥。
三、构建系统
3.1 编译工具
3.1.1 catkin编译
ROS采用catkin进行编译,Catkin 是 CMake 的进一步封装和扩展,而 CMake 则是一种元构建系统,它会生成 Makefile,再由 make 工具完成编译。
下图是 ROS 编译系统 Catkin 的大致工作流程:

ROS同时有另外一个构建工具catkin_tools。该工具是由用于构建ROS 1软件包的独立Python软件包提供的。该构建工具是在catkin_make / catkin_make_isolated之后开发的,用于并行构建多个软件包并提供显著的可用性改进。该构建工具支持构建CMake软件包并且单独构建这些软件包,以及支持跨软件包并行处理过程。
catkin build与catkin_make完全不属于一个东西,后者是对cmake命令的一个简单包装,前者是一套专用构建工具,属于catkin家族,里面包含各种工具。
3.1.2 colon编译
ROS2Bouncy 版本之前的构建工具使用的是ament_tool,之后(foxy、Humble版本)的构建工具统一使用colcon。但ROS2默认是没有安装colcon的,需要单独安装。
colcon是一个构建软件包集合的命令行工具,是ros构建工具catkin_make, catkin_make_isolated, catkin_tools 和ament_tools的迭代版本,其设计目的是为了开发一款通用的软件包构建工具,可以依赖不同的构建工具(如catkin_cmake, ament_tool等),支持不同的构建系统(如catkin, ament等)。
3.2 软件包构建
在ROS 1中,可以在单个CMake环境中构建多个软件包。虽然这可以加快构建步骤,但每个软件包都需要确保正确定义了跨软件包目标依赖关系。此外,所有软件包共享相同的命名空间,这会导致目标名称冲突等问题。
而在ROS 2中仅支持“隔离”构建,即每个软件包都是单独构建的。安装空间可以是隔离的,也可以是合并的。
3.3 运行可执行文件
在ROS 1中软件包可以在不安装它们的情况下进行构建(默认安装在devel/目录下)。从workspace结合源代码空间,系统就已经可自动找到可执行文件。但是每个软件包都必须在开发空间下,例如在环境挂钩和CMake代码中。
在ROS 2中,构建一个软件包后必须安装(在CMakeLists.txt中添加install命令)该软件包(一般安装在install/目录下,devel/变成了install/),这样才能使用该软件包。
3.4 编程语言版本
ROS的最初发行版在2007年,长期以来的支持和众多开发库的支持导致很多语言的新特性并不能良好地应用。ROS的核心是面向C++03的,而未在其API中使用C++11特性。ROS 2020年发布的Noetic版本中才首次支持了Python 3,而Python 2在2020年1月便已经停止进行支持了。
ROS 2则完全支持Python 3(≥3.5),同时ROS 2广泛使用C++11标准,并部分使用了C++14。将来,只要所有主要平台都支持,ROS 2可能会开始使用C++17标准。基于其松耦合的方式,ROS2还支持Java和Rust等编程语言。
四、核心概念
4.1 发现
ROS2 节点之间的互相发现是通过ROS2底层的中间件DDS来实现的。
节点之间相互发现的过程总结如下
- 当一个节点启动后, 它会向其他拥有相同ROS域名(ROS domain, 可以通过在~/.bashrc中设置ROS_DOMAIN_ID环境变量来修改)的节点进行广播,说明它已经上线。
- 其他节点在收到广播后返回自己的相关信息,这样节点间的连接就可以建立了,之后就可以通信了。
- 节点会定时广播它的信息,这样即使它已经错过了最初的发现过程,它也可以和新上线的节点进行连接。
- 节点在下线前它也会广播其他节点自己要下线了。
- 节点只会和具有相兼容的[服务质量]设置的节点进行通信。
4.1 进程模型(节点与进程的关系)
在 ROS1 中,节点与可执行文件紧密相关。 ROS1 中添加了一个名为 Nodelets 的新功能,以便能够在同一个可执行文件中编写多个节点,并进行进程内通信。 当硬件资源有限或要在节点之间发送大量消息(如图像和雷达数据)时,使用nodelets会减少通信延时。
ROS2的同一可执行文件中可以存在多个ROS2节点,而在ROS的一个可执行文件只能存在一个ROS节点!
在 ROS2 中,Nodelets变为Component,它是一个修改过的节点类,使用component可以处理来自同一个可执行文件的多个节点,来使用进程内通信以消除ROS2的通信开销。
由于组件仅内置在共享库中,因此它没有 main 函数,组件通常是 rclcpp::Node 的子类。 由于它不受线程控制,因此不应在其构造函数中执行任何长时间运行或阻塞的任务。
组件可以创建发布者、订阅者、服务器和客户端,还可以使用计时器来获得定期通知。
一旦创建了组件,必须通过向索引注册的方式令工具能够发现它,注册后组件库将会被加载到正在运行的进程中。使用包rclcpp_components的宏进行的注册方法(源代码最后一行)如下:
add_library(talker_component SHARED
src/talker_component.cpp)
rclcpp_components_register_nodes(talker_component "composition::Talker")
# To register multiple components in the same shared library, use multiple calls
# rclcpp_components_register_nodes(talker_component "composition::Talker2")
注意:为了让component_container能够找到所需的组件,它必须从一个已设定了相应工作区的shell中执行或启动。
4.2 生命周期节点LifecycleNode
为了解决ROS中节点启动顺序无法控制的问题,ROS2 引入了生命周期节点的概念。
ROS2中提供了两种节点类型,Node() 是和ROS1中一样的节点基类,LifecycleNode() 是可管理状态的节点基类。
生命周期节点四种具有不同的状态:Unconfigured、Inactive、Active、Finalized。 当在实际运行节点的主要功能之前需要一个设置阶段时,这非常有用。
当启动一个节点时,它最初是未配置的。 可以通过提供的接口(Service),将其转换到另一个状态,并在该节点内触发预定义的回调。
下图演示了各个状态之间是如何切换的。

从图上可以看出,LifecycleNode 类型节点切换状态是通过执行一系列的函数实现的。这些函数在继承LifecycleNode 类型节点时是需要重新实现的。
4.3 启动文件Launch
ROS1 使用 XML 编写启动文件。
ROS2可以使用 Python 编写启动文件,引入ROS2的一个python库后,就使用API 可以启动节点、检索配置文件、添加参数等,可以比以前更多地自定义启动文件。
ROS2也可以用 XML 编写ROS2 启动文件,但python启动文件已经成为ROS2默认规定。
4.4 服务Service
ROS1 中的Service是同步的,当客户端向服务器请求请求时,等待响应期间会强行阻塞程序,直到服务器响应(或失败),因而完全无法获知服务端的处理进度,更不能取消或变更请求。
ROS2中的Service是异步的,一个Server可支持多个Clinet,但每次只能响应一个Request,如下图所示。

当调用Service时,Request会添加一个回调函数,该回调函数将在服务器响应Response时触发。 在这期间,主线程不会阻塞。
Client调用Service的流程如下:

Client调用Service的异步过程总结为:
- 创建client
- 发送请求并交给中间件,中间件返回sequence_number
- client以sequence_number作为键值保存对应的promise和future用来储存响应数据
- exetutor单线程spin,当收到response时执行execute_client()
- 根据response的header中sequence_number找到client中的promise
- 将response类型转换并将其指针赋给promise
- 返回promise里的数据给node
4.5 参数Paramer
ROS1中的参数由参数服务器处理,而参数服务器本身由ROS master 处理。
ROS2的参数是由Service构建出来,因为ROS2 中没有ROS master,也就没有参数服务器。
ROS2不再有全局参数,每个参数都特定于一个节点,每个节点声明和管理自己的参数,这些参数在节点被杀死时被销毁。
当启动一个节点时,该节点会创建一些 ROS2 Service,通过这些Service从终端或其他节点与这个节点的参数进行交互,这个节点则通过参数回调服务来修改自身的参数。
4.6 动作(action)
ROS1 中Action机制是运行服务端和客户端异步请求,采用无阻塞的编程方式。
client和server之间通过actionlib定义的“action protocol”进行通讯。这种通讯协议是基于ROS的Topic机制实现的,为用户提供了client和server的接口,接口如下图所示:

client向server端发布任务目标以及在必要的时候取消任务,server会向client发布当前的状态、实时的反馈和最终的任务结果。
- goal:任务目标
- cancel:请求取消任务
- status:通知client当前的状态
- feedback:周期反馈任务运行的监控数据
- result:向client发送任务的执行结果,这个topic只会发布一次。
ROS2中 Action是一个上层的沟通机制,Action包括三个部分:目标、结果和反馈。
Action是可抢占的模式,即可以在执行时将其取消,与返回单个响应的Service不同,它们还提供稳定的反馈,即过程状态反馈。
一个常见的例子:一个机器人系统使用Action来进行导航,Action的目标可以是告诉机器人到某一个位置,当机器人导航到这个位置, 它中途不断反馈当前位置。,最后当他到达目标点的时候会返回一个结果。

Action是由Topic和Services共同构建出来的,一个Action = 三个服务+两个话题。
三个服务分别是:
- 目标传递服务
- 结果传递服务
- 取消执行服务
两个话题:
- 反馈话题(服务端发布,客户端订阅)
- 状态话题(服务端发布,客户端订阅)
下图中的取消执行服务和状态话题未体现出来,但仍然存在!

4.7 服务质量Qos(Quality of service)
ROS1未考虑不稳定网络情况下的通信可靠性,导致TCPROS通信在无线通信场景下,可能因网络连接导致导致后退、重传输和延迟
ROS2 引入了 QoS,即服务质量。通过配置不同的服务质量策略(Quality of service policy),ROS2可以像TCP一样可靠,也可以像UDP那样尽力而为。在不稳定的网络环境下,“尽力而为”策略将更合适。ROS2引入服务质量(QoS)来公开这些设置,以优化可用带宽和延迟。
常用的Qos策略有用Best-effort策略和Reliable策略。
- Reliable策略:发布者将继续发送数据,直到接收者确认收到为止。
- Best-effort策略:发布者将不断发送数据,不论接收者是否收到数据。
注意:如果两个节点的Qos设置不兼容,将无法通信。
ROS2设置了默认情况下的ROS2 通信(Topic、Service等)的 QoS参数,因此使得ROS2的通信与 ROS1具有中相同的行为:
- 任何订阅主题的节点都不会收到之前的消息,只会收到订阅后发布的消息
- 与 TCP 一样,消息保证被传递
- 可以为等待处理的已传递消息设置队列大小
如果需要处理有损耗的无线网络和/或较大的消息带宽,那么需要根据实际场景设置QoS策略。
4.8 接口Interface
ROS1中的消息类型(msg)、服务类型(srv)和动作类型(action)并没有统一定义为接口,并且不同编程语言对这些类型的处理工具不一样。
ROS2引入通信接口(interface)概念,接口用来描述不同节点之间交换信息的数据结构,这些数据结构以与编程语言无关的方式进行定义。
ROS2的通信接口包含话题(Topic)的消息类型(msg)、服务(Service)的服务类型(srv)、动作的(Action)动作类型(action),这些接口都通过对应接口文件进行定义。
ROS2使用接口描述性语言(Interface Description Language,IDL)来映射通信接口,通过接口描述性语言可以自动地生成不同目标语言的接口类型的源码。
IDL利用一个接口文件为不同编程语言生成相同的接口,例如发布消息或订阅主题,为每种消息类型生成代码,以msg文件为例,具体流程如下:

4.8 工具
4.8.1 命令行工具
ROS1的命令行工具的语法是 rostopic echo xxx、rosmsg info xxx、rosbag record xxx等形式。
ROS2的命令行工具的语法是ros2 topic echo xxx、ros2 msg info xxx、ros2 bag record xxx等形式。
4.8.2 rqt可视化工具
ROS1和ROS2命令保持一致!
4.8.3 rviz
ROS1是rviz,ROS2是rviz2,只是名称变了,其它保持一样。
4.8.4 Gazebo
ROS1采用Gazebo作为仿真平台,Gazebo 11为最终版将于2025年停止维护。
ROS2采用Ignition作为仿真平台,Ignition为Gazebo新一代版本。Gazebo和Ignition都是独立于ROS或ROS 2的项目;
Ignition的核心仿真器仍为为Gazebo,提供了基于开发库和云服务等丰富全面的工具箱,提供了一种全新的仿真方式,进一步简化仿真。
ROS2 Foxy的版本对应的为Ignition的版本为Citadel;Foxy LTS版本支持时间2020-2023,Citadel LTS版本支持时间2020-2025。
参考:
ROS与ROS2的通信模型区别:https://www.guyuehome.com/34382
ROS1的action工作机制:https://blog.csdn.net/x_r_su/article/details/53098679
ROS2的Service调用过程:https://blog.csdn.net/qq_16893195/article/details/113571858
接口的头文件生成:https://docs.ros.org/en/foxy/Concepts/About-Internal-Interfaces.html
Ignition资料:https://blog.csdn.net/ZhangRelay/article/details/107354023
