【ROS2概念】系列(四)——DDS中间件架构详细解析

目录

  • 目录
  • 一、DDS规范
    • 1.1 DDS规范
    • 1.2 DDSI-RTPS规范
    • 1.3 IDL规范
  • 二、DDS的通信架构
    • 2.1 DDS组成部分
    • 2.2 DDS、DCPS、DDSI-RTPS三者关系
  • 三、DCPS模型
  • 四、RTSP协议
    • 4.1 背景
    • 4.2 组成部分
      • 4.2.1 结构(Structure)模块
      • 4.2.2 消息(Messages)模块
      • 4.2.3 行为(Behavior)模块
      • 4.2.4 发现(Discovery)模块
    • 4.3 RTSP实体的实现
      • 4.3.1 RTSP中实体的关系
      • 4.3.2 实现RTSP实体的Class
    • 4.4 RTPS的数据格式
    • 4.5 RTPS通信的过程
    • 4.6 RTPS的发现机制
      • 4.6.1 SPDP
      • 4.6.2 SEDP
      • 4.6.3 移除

一、DDS规范

DDS规范包含六大类,分别是

  • 核心规范
  • 类型语法和语言映射(IDL)规范
  • 应用程序接口(API)规范
  • 扩展规范
  • 网关规范
  • 正在进行研究的规范(未发布)。

此章节仅介绍DDS的核心规范与接口规范。

DDS核心规范包括:

  • DDS v1.4:DDS 规范描述了以数据为中心的发布-订阅 (DCPS) 模型,该模型用于分布式应用程序通信和集成。
  • DDSI-RTPS v2.3:RTPS规范定义了实时发布-订阅协议 (RTPS),此协议为DDS标准中互操作有线协议。
  • DDS-XTypes v1.3:此规范定义DDS数据类型系统以及DDS数据的序列化表示方法。
  • DDS-Security v1.1:此规范为DDS实现定义了安全模型和服务插件接口 (SPI) 架构。

另外IDL v4.2规范定义了IDL(Interface description language),一种用于以独立于编程语言的方式定义数据类型和接口的语言。这不属于DDS标准,但DDS依赖于它。

1.1 DDS规范

DDS 规范的开发始于 2001 年。它由软件框架公司 Real-Time Innovations (RTI) 和法国国防公司Thales Group开发。2004 年,对象管理组(OMG) 发布了 DDS 1.0 版。1.1 版于 2005 年 12 月发布,1.2 版于 2007 年 1 月发布,1.4 版于 2015 年 4 月发布。

DDS规范总共有四个正式版本,如下表所示:

OMG发布的只是DDS标准,而标准的实现是由各个DDS提供商完成,其中有商用的如RTI,也有开源的如Object Computing的OpenDDS、eProsima的FastDDS。

DDS 规范描述了两个级别的接口

  • DCPS:较低的以数据为中心的发布-订阅 (DCPS) 级别,旨在将正确的信息有效地传递给正确的接收者。
  • DLRL:一个可选的更高数据本地重建层 (DLRL),它允许将 DDS 简单地集成到应用层中。

DCPS规范是API标准,保证了不同DDS实现的应用程序的可移植性。从 2015 年的 DDS 版本 1.4 开始,可选的 DLRL 层被移至单独的规范。因此DCPS 1.4规范成为DDS的核心规范之一。

1.2 DDSI-RTPS规范

DDSI-RTPS:全称“Real-time Publish Subscribe Protocol DDS Interoperability Wire Protocol”,它是DDS Wire-protocol。是DDS实施互操作性(标准化)协议。(以下将DDSI-RTPS,简称为RTPS)

DDS 互操作性有线协议规范,即DDSI-RTPS规范,确保使用一个供应商的 DDS 实现发布的关于主题的信息可供使用相同或不同供应商的 DDS 实现的一个或多个订阅者使用,即不同厂商的DDS产品可相互兼容,具备互操作性。DDSI-RTPS规范 2.0 版于 2008 年 4 月发布,2.1 版于 2010 年 11 月发布,2.2 版于 2014 年 9 月发布,2.3 版于 2019 年 5 月发布。

其中ROS2使用的Fast-DDS目前(2022.11)仍然采用的DDSI-RTPS2.2规范。

DDSI-RTPS规范总共有四个正式版本,如下表所示:

其中2.3版本相较于2.2版本主要修改了以下部分:

8.2.1中Participant类与Endpoint类关系由2.2版本中的无关系变为组合关系;Participant类增加guidPrefix成员变量,Endpoint类增加endpoint成员变量;
8.2.3 RTPS CacheChange类增加inlineQoS成员变量;
8.2.4 RTPS Entity类增加了同一参与者内部端点组的GUID信息,主要是指Publisher和Subscriber的GUID;同时Endpoint类也继承Entity类;
8.2.5 RTPS Participant类与Endpoint类之间增加了一个Group类;
8.3.2关于数据类型定义添加了GroupDigest_t类型;
8.3.3 RTPS消息组成结构部分去除NoKeyData以及NoKeyDataFrag类型;
8.3.4 RTPS消息接收器(RTPS Message Receiver)增加对HeaderExtension的依赖;
8.3.5 RTPS子消息元素(RTPS SubmessageElements)增加了KeyHashPrefix、KeyHashSuffix以及GroupDigest数据类型的支持;将2.2版本的Flags数据类型改为StatusInfo类型;将SerializedPayload以及SerializedPayloadFragment类型合并为SerializedData类型;
8.3.7.2 Data报文类型添加NonStandardPayloadFlag字段;
8.3.7.3 DataFrag报文类型添加NonStandardPayloadFlag及KeyFlag字段;
8.3.7.4 Gap报文类型添加GroupInfoFlag、gapStartGSN以及gapEndGSN字段;
8.3.7.5 Heartbeat报文类型添加GroupInfoFlag、currentGSN、firstGSN、lastGSN、writerSet以及secureWriterSet字段;
8.4.7.1 RTPS Writer类增加dataMaxSizeSerialized成员变量;RTPS Writer类的new_change方法增加inlineQos参数。
8.4.7.2 RTPS StatelessWriter类去除resendDataPeriod数据成员;
8.4.7.5 RTPS ReaderProxy类增加remoteGroupEntityId成员;
8.4.10.4 RTPS WriterProxy类增加dataMaxSizeSerialized和remoteGroupEntityId成员;
8.4.12.1 Best-Effort StatefulReader 行为变换图改变;
8.4.13.4 Participant Message Data数据组成添加kind字段;
8.4.15.7 在HeartbeatFrag和NackFrag报文中增加count;
8.5.3.2 SPDPdiscoveredParticipantData数据类型增加domainId、domainTag 和builtinEndpointQos字段;
8.7.5 添加Group Ordered Access实现;
9.3.1.2 EntityId_t添加Writer Group和Reader Group实体ID类型;
9.6.3 In-line QoS增加PID_GROUP_COHERENT_SET、PID_GROUP_SEQ_NUM、PID_WRITER_GROUP_INFO以及PID_SECURE_WRITER_GROUP_INFO等字段支持;
10 数据串化封装增加CDR2_BE、CDR2_LE、PL_CDR2_BE、PL_CDR2_LE、D_CDR_BE、D_CDR_LE和XML 等版本字段。

1.3 IDL规范

DDS中,可以被交换的数据是sturct(结构体)。例如我们可以创建一个包含特定领域的结构体,比如:

struct MyData {
  long counter;
  string message;
  double threshold;
};

这个结构描述了一段可以通过dds发送和接收的数据,换句话说,就是topic(主题)。

我们必须考虑opendds能够在用不同语言创建的进程之间交换数据。例如,C++ Publisher发送一个主题,它可以被 Java 进行接收。基于这个原因,我们必须定义交换C++和Java数据的结构。为了避免这个问题,使用.idl文件在一个单独的文件中定义主题文件格式。当我们完成创建它时,可以从这个文件创建Java和C ++中的所有必需的源文件。我们以不可知的格式定义主题,然后使用一些OpenDDS工具创建我们需要的C ++或Java文件。有了这个,我们可以仅创建一次主题,以避免错误,因为数据仅在同一个地方被定义了一次。换句话说,当我们决定创建一些要交换的主题时,我们需要:

  • 定义主题的字段
  • 在.idl文件中定义主题
  • 编译用于生成C ++文件的.idl文件
  • 编译用于生成Java文件的.idl文件

当然,如果我们只使用一种语言,我们不需要为其他语言创建文件。

下表中,我们将显示可以在字符串内使用的所有数据类型:

作为一个例子,我们可以尝试创建一个用于检查数据的IDL消息。创建一个test.idl文件,并复制以下内容:

module Sample {
    struct DataTypesList 
    {
      boolean               booleanValue;
      char                  charValue;
      wchar                 wcharValue;
      string                stringValue;
      wstring               wstringValue;
      octet                 octedValue;
      short                 shortValue;
      unsigned short        unsignedShortValue;
      long                  longValue;
      unsigned long         unsignedLongValue;
      long long             longLongValue;
      unsigned long long    unsignedLongLongValue;
      float                 floatValue;
      double                doubleValue;
      fixed                 fixedValue;
    };
}; // module Sample

将其保存,并使用IDL命令build。

opendds_idl.exe .\file.idl -o .
tao_idl.exe -Sg file.idl
tao_idl.exe -Sg fileTypeSupport.idl -o .

编译之后得到一堆文件,打开fileC.h 我们可以发现C++结构。

struct  DataTypesList
{
  // TAO_IDL - Generated from
  // be\be_type.cpp:307
  typedef                   DataTypesList_var _var_type;
  typedef                   DataTypesList_out _out_type;
  static void               _tao_any_destructor (void *);
  ::CORBA::Boolean          booleanValue;
  ::CORBA::Char             charValue;
  ::CORBA::WChar            wcharValue;
  ::TAO::String_Manager     stringValue;
  ::TAO::WString_Manager    wstringValue;
  ::CORBA::Octet            octedValue;
  ::CORBA::Short            shortValue;
  ::CORBA::UShort           unsignedShortValue;
  ::CORBA::Long             longValue;
  ::CORBA::ULong            unsignedLongValue;
  ::CORBA::LongLong         longLongValue;
  ::CORBA::ULongLong        unsignedLongLongValue;
  ::CORBA::Float            floatValue;
  ::CORBA::Double           doubleValue;
};

正如所看到的,idl编译器转换自定义c ++类型中的idl数据类型,允许执行某种抽象。其使用常见的数据类型映射到C++中。

二、DDS的通信架构

2.1 DDS组成部分

根据前述DDS的核心规范可知,DDS的核心组成部分为DDS规范,RTSP规范和IDL规范

注意:下文中DDS指代意义与上下文有关:

  • 有时是指数据分发服务(Data Distribution Service)总体名称

  • 有时是指DDS中的核心规范DDS 1.4,该规范用来描述以数据为中心的发布-订阅 (DCPS) 模型,在该场景下可以简单理解DDS v1.4为DCPS模型

下图中从下往上可分为三层,分别是网络通信协议DDS应用程序,其中红白线虚线框内是DDS的核心标准具体实现:

  • DDS v1.4规范描述了以数据为中心的发布-订阅 (DCPS) 模型,该模型用于分布式应用程序通信和集成。

  • DDSI-RTPS v2.2 - 定义了实时发布-订阅交互通信协议(RTPS);(v2.3是2019年发布,Fast-DDS目前2022.11采用的仍然是v2.2

  • IDL v4.2 - 定义了IDL,一种用于以独立于编程语言的方式定义数据类型和接口的语言。这不属于DDS标准,但DDS依赖于它。

2.2 DDS、DCPS、DDSI-RTPS三者关系

DDS是指数据分发服务(Data Distribution Service)总体名称

DDS中的核心规范DDS 1.4,该规范用来描述以数据为中心的发布-订阅 (DCPS) 模型,在该场景下可以简单理解DDS v1.4为DCPS模型。

DDSI-RTPS:全称“Real-time Publish Subscribe Protocol DDS Interoperability Wire Protocol”,它是DDS Wire-protocol。是DDS实施互操作性(标准化)协议。(以下将DDSI-RTPS,简称为RTPS)

这里的Wire Protocol指的是一种传输机制。A wire protocol is the mechanism for transmitting data from point a to point b.

从下图可以看出,DDS(实际是DCPS模型)是一种应用通信传输框架,而DDSI-RTPS则是一种传输协议,DDS是DDSI-RTPS的上层

DDS在2017年发布了DDS-RPC规范,使得DDS能够基于发布-订阅模型实现远程过程调用(RPC),满足SOA架构的需求。

三、DCPS模型

3.1 组成结构

如上图所示, DCPS模型由七个DCPS实体构成: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构造的全局数据空间逻辑上统一,物理上分布式而组成。

3.2 通信流程

在DCPS模型中,一个或多个DataWriters通过Global Data Space发布指定类型数据的Topic(Topic名称在域中是唯一的)。一个或多个DataReader通过Global Data Space按Topic名称标识数据对象,以便订阅该Topic。在此事务之后,DataWriter使用分布式系统中的实时发布/订阅(RTPS)协议连接到DataReader

DataWriter和DataReader之间的数据传输根据QoS策略在RTPS协议中执行。每个DCPS实体根据唯一的用户指定的QoS策略管理数据样本DCPS中间件负责基于QoS策略的分布式系统中的数据传输。在不考虑详细的传输实现的情况下,DDS用户将代码生成为Domain Participant,包括使用DDS API的QoS策略。因此,用户可以仅关注其目的并确定轻松满足实时约束的方法。

3.3 Qos策略

表1显示了ROS2支持的QoS策略的详细信息。在DDS中,还有许多其他QoS策略,ROS2应该支持扩展其功能。

所有DCPS实体都有QoS Policy,表示其数据传输行为。每个数据事务都可以通过许多QoS策略选项在不同的粒度级别进行配置。图4显示了遵循QoS策略的DDS数据的RELIABLE Policy传输示例。

四、RTSP协议

4.1 背景

DDS的标准中并不包含传输层协议,不同的DDS实现可能使用不同的消息交互方式,甚至使用不同的传输层协议,比如不用TCP/UDP,用串口,这就可能导致来自不同厂家的DDS实现是不能互操作的。而随着DDS在大型分布式系统中的应用越来越广泛,制定统一传输层标准的需求越来越强烈。

RTPS(Real-Time Publish Subscribe)协议就是在此背景下诞生,它主要为了满足工业自动化领域大规模分布式系统的需求,也能够很好的契合DDS协议特点。RTPS规范中定义了消息格式,各种使用场景下的消息交互方式等。下图为不同厂商(vendor A、B、C、D)的DDS通过RTSP协议进行交互。

不同vendor的DDS可通过RTSP协议进行交互

RTPS基于多播、无连接的传输模型,这个模型可以映射到不同的传输协议上,如UDP/IP(这也是目前RTPS标准中唯一被标准化的传输协议),串口(未被标准化,但是也存在一些厂商的产品中)等。除此之外,基于TCP的传输,以及基于TSN(Time-Sensitive Networks)的传输的相关标准也在制定中。很多DDS的实现支持除UDP/IP之外的多种传输协议,但并没有遵循统一的标准,因此不同供应商的DDS实现之间可能存在互操作问题。

4.2 组成部分

RTPS协议由PIM(Platform Independent Model,平台独立模型)和一组PSM(Platform-Specific Model,平台特定模型)描述。(来自OMG RTPS协议规范文档)

PIM包含四个模块:结构,消息,行为和发现,如下图

  • 结构(Structure)模块定义通信端点
  • 消息(Messages)模块定义这些端点可以交换的消息集合
  • 行为(Behavior)模块定义合法交互集(消息交换)以及它们如何影响通信端点的状态
  • 发现(Discovery)模块定义如何自动发现和配置实体。

PSM负责提供PIM与UDP(或者说底层平台)之间的映射,主要包括各种消息格式。

4.2.1 结构(Structure)模块

由于RTPS是用于实现DDS应用的有线协议,因此每个DDS概念或实体自然地映射到RTPS实体。所有RTPS实体都与RTPS域相关联,RTPS域表示包含一组参与者的单独通信平面。每个RTPS参与者可以包含两种不同类型的本地端点: Writers 和 Readers. 。这两个端点通过发送RTPS消息在RTPS网络中交换信息。Writers将本地可用信息发送给Readers,Readers可以请求或确认数据。

RTPS结构

4.2.2 消息(Messages)模块

消息模块定义RTPS写入器和读取器之间的原子信息交换的内容。RTPS消息由标题和许多Submessage组成。标头将消息标识为RTPS协议的一部分,以及正在使用的协议版本和发送消息的供应商。它还标识正在发送消息的参与者。

每个Submessage都由Submessage Header和一系列Submessage元素组成。选择此结构是为了允许扩展Submessages的词汇表和每个Submessage的组成,同时保持向后兼容性。Submessage Header包含子消息Id,用于标识子消息的类型,子消息长度(以字节为单位)和子消息标志。有十二种不同类型的子消息。有关所有消息的完整描述,其组成和解释,请参阅OMG RTPS规范文档。重要的信息是:

Data: 此子消息从Writer发送到Reader,其中包含有关对属于Writer的数据对象的更改的信息。此更改可以是值(添加新信息)或生命周期(先前发送的数据不再有效)。

Heartbeat: 此子消息从Writer发送到Reader,传达Writer目前可用的CacheChanges。

AckNack: 此子消息从Reader发送到Writer,并允许Reader通知Writer它已收到哪些更改以及哪些更改仍然丢失。它可以用来做正面和负面的确认。

4.2.3 行为(Behavior)模块

此模块描述了Writer和Reader之间可能发生的有效消息交换。它还根据每条消息定义Writer和Reader状态的变化。可以在OMG RTPS规范文档中找到完整的规则。设置这些规则是为了确保不同实现之间的互操作性。

4.2.4 发现(Discovery)模块

此模块描述了使参与者能够获取有关域中所有其他参与者和端点存在和属性信息的协议。这种信息交换称为metatraffic。一旦发现远程端点,就可以相应地配置本地端点以建立通信。

发现协议分为两层:参与者发现协议(PDP)和端点发现协议(EDP)

PDP指定参与者如何相互发现。发现后,参与者使用EDP交换有关其端点的信息。不同的供应商可以实现多个发现协议,但是为了确保互操作性,所有供应商必须实现一个PDP和一个EDP。

可以在规范文档中找到发现模块的完整描述。然而,这种发现机制最重要的特征是它允许简单的即插即用连接,而无需用户进行任何配置。

4.3 RTSP实体的实现

4.3.1 RTSP中实体的关系

RTPS 中定义了一个 Domain 的概念,它定义了一个单独的通信平面。几个域可以同时独立地共存。一个域包含任意数量的 RTPS Participant,即能够发送和接收数据的元素。

每个Participant拥有若干Endpoint,Endpoint又分为Reader和Writer两大类型。Endpoint是RTPS最基本的通信单元。

在应用程序里用户操作的是DDS实体(DataWriter与DataReader),每个DDS实体对应RTPS的实体,RTPS 实体是应用程序可见的 DDS 实体使用的协议级端点,RTPS与DDS实体通过HistoryCache桥梁进行沟通。其中HistoryCache是一个数组,里面包含若干个CacheChange。

在发布端,DDS DataWriter 的每次写入操作都会在其对应的 RTPS Writer 的 HistoryCache中添加一个 CacheChange。RTPS Writer 随后将 CacheChange 传输到所有匹配的 RTPS Reader的HistoryCache。

在接收端,RTPS Reader 通知 DDS DataReader 新的 CacheChange 已到达 HistoryCache,此时DDS DataReader可以选择使用 DDS 读取或获取 API 访问它。

4.3.2 实现RTSP实体的Class

RTSP协议具体实现过程中用到了以下自定义的Class,所有 RTPS实体都派生自 RTPS 实体类。

Class Purpose
Entity 所有 RTPS 实体的基类。 RTPS 实体表示对网络上的其他 RTPS 实体可见的对象类别。 因此,RTPS 实体对象具有全局唯一标识符 (GUID),可以在 RTPS 消息中引用。
EndPoint 表示可以作为通信端点的对象的 RTPS 实体的特化。 也就是说,可以作为 RTPS 消息的源或目标的对象。
Participant 共享公共属性并位于单个地址空间中的所有 RTPS 实体的容器
Writer RTPS 端点的特化,表示可以作为通信 CacheChanges 的消息源的对象。
Reader 表示可用于接收与 CacheChanges 通信的消息的对象的 RTPS 端点的专门化。
HistoryCache 用于临时存储和管理数据对象更改集的容器类在 Writer 端,它包含 Writer 对数据对象所做的更改的历史记录。不必保留所有更改的完整历史记录。相反,需要的是为现有和未来匹配的 RTPS 阅读器端点提供服务所需的部分历史记录。所需的部分历史记录取决于 DDS QoS 以及与匹配的 Reader 端点的通信状态。在 Reader 端,它包含匹配的 RTPS Writer 端点对数据对象所做更改的历史记录。不必保留曾经收到的所有更改的完整历史记录。相反,需要的是包含根据需要叠加从匹配写入器接收到的更改以满足相应 DDS DataReader 的需求。此叠加的规则和所需的部分历史记录量取决于 DDS QoS 以及与匹配的 RTPS 写入器端点的通信状态。
CacheChange 表示对数据对象所做的单独更改。 包括数据对象的创建、修改和删除
Data 表示可能与对数据对象所做的更改相关联的数据

RTPS传输协议由Entity、Participant、Endpoint、Writer、Reader、HistoryCache以及CacheChange组成。

应用通过DDS层实体来访问对应的RTPS实体,HistoryCache是他们之间的桥梁。

4.4 RTPS的数据格式

Endpoint是RTPS的通信基本单元,他们之间传输固定格式的Message数据,RTPS中每个实体拥有一个全局唯一标识符(GUID),GUID由前缀和实体ID两部分构成,其中前缀就在每个消息的Header中,后面的每个Submessage只需要实体ID(Entity ID)即可。

Message整体格式如下图所示:

一个Message有一个Header,Header包含RTPS的标识、RTPS协议版本、实现版本以及消息的GUID前缀GuidPrefix

Header后面是Submessage,每个Submessage包含SubmessageHeader和SubmessageElement。SubmessageHeader包含此Submessage的类型,大小端类型以及数据长度等信息。

SubmessageElement表示各种消息的字段,不同的消息类型具有相应的字段。Submessage有十二种不同类型的子消息,下表为消息的完整描述,其组成和解释:

表中可以看出,通过AckNack、Heartbeat以及NackFrag可以实现可靠传输,接下来看一下数据的通信过程。

4.5 RTPS通信的过程

这张图描述了user(比如一个ROS2发布者和订阅者)之间通信的大致过程:

图中的步骤描述如下

  1. 用户调用DDS中间件DataWriter实体,执行write操作
  2. DataWriter通知RTPS实体Writer创建一个CacheChange
  3. DataWriter调用add_change将该Cachechange放到Writer的HistoryCache中
  4. 用户操作结束
  5. RTPS的Writer将CacheChange发送给RTPS Reader,使用的消息是上一章节的Data类型,并且发送HeartBeat给Reader用来获取Reader接收状态
  6. RTPS reader收到Data消息后,将CacheChange塞到HistoryCache里面
  7. DDS通知用户有新的Cachechange,ROS2里面用DDS的WaitSet接口,然后用户使用take操作来获取数据
  8. DDS DataReader从HistoryCache访问CacheChange
  9. RTPS Reader发送AckNack表明确认收到消息,这和用户的take操作是独立的,可能同时也可能先发生
  10. Writer(stateful)记录下确收的CacheChange
  11. Reader这一方的用户调用return_loan操作,表明用户不再使用之前take的该消息
  12. DDS DataReader调用remove_change操作将该CacheChange从Reader的HistoryCache中移除
  13. DDS DataWriter根据is_acked_by_all判断CacheChange是否被所有期望接收的Reader确收,如果是的话就将该序号的CacheChange从Writer的HistoryCache中删除,HistoryCache的保留数量由QoS的DURABILITY确定

Writer在上面的例子中采用的StatefulWriter,实际中还有StatelessWriter,这需要根据具体的网络规模、硬件资源等进行选择。

4.6 RTPS的发现机制

DDS的另一个特征就是支持自动发现,RTSP协议要求必须实现基本的两种基本发现协议:SPDP和SEDP,他们适合中小型网络场景:

  • Simple Participant Discover Protocol发现协议(SPDP)
  • Simple Endpoint Discover Protocol发现协议(SEDP)

4.6.1 SPDP

SPDP对每个Participant预留两个内置Endpoint:SPDPbuiltinParticipantWriter 和SPDPbuiltinParticipantReader。

SPDPbuiltinParticipantWriter是一个best-effort的 StatelessWriter,不进行可靠传输且不维护Reader的接收状态。它周期性地向预先配置好的一系列locators(目标地址,多播或者单播地址)交换其HistoryCache里的SPDPdiscoveredParticipantData数据。

SPDPbuiltinParticipantWriter不断发布自己当前发现了的Participant,然后将自己发现的信息和其他人交换,这样每个Participant逐渐就都知道对方了。

发现

经过Discovery后,Writer与Reader相互匹配。可以进行数据的传输(订阅的Topic)。其数据传输的大致过程为

  1. DataWriter 添加CacheChange 到HistoryCache中(本地缓存)
  2. DataWriter将CacheChange发送到相应的DataReader的HistoryChace(本地缓存)
  3. HistoryCache(Reader)通知Reader有新的CacheChange。
  4. Reader读取数据。
传输过程

上面提到SPDPbuiltinParticipantWriter往预先配置好的locators(网络地址)分享信息,这些locators其实是一些SPDP保留的熟知端口,根据平台不同定义成了两个宏:

  • 单播:SPDP_WELL_KNOWN_UNICAST_PORT
  • 多播:SPDP_WELL_KNOWN_MULTICAST_PORT

4.6.2 SEDP

一旦通过SPDP发现了另一个Participant,那么就认为对方Participant存在内置Endpoint,并与对方的内置Endpoint进行配对即可,SEDP的内置Endpoint配对应当采用的可靠传输了。

SEDP预留的内置Endpoint和数据类型如上图所示,其中TopicWriter和TopicReader是可选的。

4.6.3 移除

SPDPdiscoveredParticipantData中包含leaseDuration字段,这个字段表示一个Participant在这个时间周期内发布一次SPDPdiscoveredParticipantData则认为这个Participant还是或者的,如果超过leaseDuration没发送SPDPdiscoveredParticipantData则认为这个Participant已经下线,相关的资源以及Endpoint可以被释放掉。

参考:
IDL介绍:https://blog.csdn.net/wyc12306/article/details/79577389
DDS规范参考:https://www.platforu.com/DetailsPage?id=17
RTPS的概念理解:https://blog.csdn.net/zxc024000/article/details/107605470
[译]RTSP PIM的四个结构:https://www.jianshu.com/p/6ad9399656ad
[英]RTSP PIM的四个结构:https://www.eprosima.com/index.php/resources-all/whitepapers/rtps
RTPS协议数据传输细节:https://blog.csdn.net/qq_16893195/article/details/113937167
RTPS协议的PIM和PSM两组模型:https://blog.csdn.net/xllhd100s/article/details/113087123
DDS-RPC通信机制:https://blog.csdn.net/zxc024000/article/details/106070089
FastDDS执行的规范版本:https://www.eprosima.com/index.php/products-all/eprosima-fast-dds

你可能感兴趣的:(【ROS2概念】系列(四)——DDS中间件架构详细解析)