通信中间件DDS介绍一
通信中间件DDS介绍(一)
一、前言
DDS是分布式实时的网络通信中间件。用于屏蔽操作系统、底层硬件、体系架构的差异性,以帮助应用人员在开发、维护、升级等阶段缩短时间和人力成本,简化应用程序的开发与调度操作等。DDS能在多种传输网络环境中对即时数据进行快速、可预测的数据分发。
用户可以通过DDS实现下列功能:
- 适用于分布式系统的数据传输,不会因为单点故障导致整个通信历史的丢失以及瓶颈点通信故障的发生。
- 实现一对多、多对多、多对一的通信方式,应用程序在DDS标准下发布/订阅数据;
- 自定义应用程序操作,满足各种实时性、可靠性、服务质量的目标;
- 应用透明,容错,鲁棒性强;
- 使用不同传输方式。
二、数据发布与订阅
1 概述
数据发布与订阅(Data-Centric Publish-Subscribe,DCPS)定义了应用如何发布和订阅一组数据对象的功能集合。发布应用端创建可以被标识的数据对象,并对这些数据对象赋值,同时将数据对象发布出去。订阅应用端可以识别数据对象,并对感兴趣的数据对象进行订阅,获取数据对象中的具体内容。无论是发布应用端,亦或是订阅应用端,都可以定义主题,通过主题进行联系,同时为DCPS中的实体设置特定的Qos策略,使得模型更加符合应用场景的特征。
2 平台无关模型
平台无关模型(Platform Independent Model,PIM)是区别于PSM定义的模型。PIM定义了操作的返回错误码ReturnCode_t,PSM层将具体的操作错误映射到对应的PIM错误码,PIM错误码如下表1所示。
表 1 PIM错误码
编码 | 含义 | 数值 |
RETCODE_OK | 操作成功 | 0 |
RETCODE_ERROR | 错误,但错误原因未知 | 1 |
RETCODE_UNSUPPORTED | 当前不支持该方法 | 2 |
RETCODE_BAD_PARAMETER | 参数错误 | 3 |
RETCODE_PRECONDITION_NOT_MET | 调用当前函数的前置条件不满足 | 4 |
RETCODE_OUT_OF_RESOURCES | 资源不足 | 5 |
RETCODE_NOT_ENABLED | 实体未使能,不允许使用该方法 | 6 |
RETCODE_IMMUTABLE_POLICY | 应用尝试修改不可变QoS策略 | 7 |
RETCODE_INCONSISTENT | 用户指定的QoS策略不兼容 | 8 |
RETCODE_ALREADY_DELETED | 实体已经被删除 | 9 |
RETCODE_TIMEOUT | 函数超时 | 10 |
RETCODE_NO_DATA | 没有数据 | 11 |
RETCODE_ILLEGAL_OPERATION | 非法的调用 | 12 |
简单的PIM模型如图1所示。Publisher是负责数据发布的实体,可以对不同的数据类型进行发布操作,DataWriter是Publisher针对某一特定数据类型数据具体发布操作的实体。Subscriber是负责数据订阅的实体,接收来自Publisher的数据,并递交给应用程序,Subscriber可以对不同类型的数据进行订阅操作,DataReader对Subscriber中某一具体的数据类型数据进行捕获。Topic是用于标识发布操作和订阅操作匹配的实体,其关联一个在通信域中唯一的名字,同时映射到一类数据类型。Publisher与Subscriber间进行通信必须关联到相同Topic。
图 1 简单的PIM模型示意图
图2是UML语言描述的有关PIM层涉及到的所有通信实体。QosPolicy为应用提供了一种通用的机制,该机制用于控制向应用提供的服务特点,并且具备可剪裁的能力。
图 2 PIM层通信实体的UML模型
Listener提供了一种异步通信的方式,通知应用程序中间件的有关状态,应用通过注册感兴趣的异步事件,达到异步接收和处理这些事件的目的。这些异步事件包括:数据到来、对等实体间Qos不兼容等等。
StatusCondition与WaitSet联合使用,可以为应用提供另一种可选的对等实体间通信的方式。
所有实体均与一个DomainParticipant相关,一个DomainParticipant表示一个Domain中应用程序的本地成员关系,PIM层要求必须同处于一个Domain中的应用或者实体才能进行通信,处于不同Domain的应用无法通信。
五大模块及接口
DCPS由五个功能模块组成,分别是:域模块、发布模块、订阅模块、主题模块以及基础设备模块,如图3所示。
图 3 DCPS层的五大功能模块
基础设施模块(Infrastructure Module)定义了一些抽象的方法和接口,由其他模块继承和重新实现;
域模块(Domain Module)定义了DomainParticipant类,作为中间件服务的总入口,其他模块在该模块基础上进行实例化;
主题模块(Topic Module)包含Topic类、ContentFilteredTopic类以及MultiTopic类,同时定义了有关Topic的一些接口;
发布模块(Publication Module)包含Publisher类、DataWriter类,以及PublisherListener和DataWriterListener的接口,涵盖所有关于发布方的功能;
订阅模块(Subscription Module)包含Subscriber类、DataReader类、ReadCondition类和QueryCondition类,以及SubscriberListener和DataReaderListener的接口,涵盖所有关于订阅方的功能。
- 基础设施模块
基础设施模块由下述内容构成:
- Entity
- DomainEntity
- QosPolicy
- Listener
- Status
- WaitSet
- Condition
- GuardCondition
- StatusCondition
基础设施模块的UML模型如图4所示,图中标注了基础设施模块常用类的基本方法。
图 4 基础设施模块UML模型示意图
Entity是DCPS层所有实体的基类,所有实体由该基类派生得到,提供了包括设置Qos、获取Qos、设置listener、使能实体等在内的多个操作。Status类是所有状态类的基类,每一个具体的状态类由此派生。WaitSet与Condition一起使用可以实现同步接收功能。
- 域模块
域模块由下述内容构成:
- DomainParticipant
- DomainParticipantFactory
- DomainParticipantListener
域模块的UML模型如图5所示,图中标注了域模块常用类的基本方法。
图 5 域模块UML模型示意图
DomainParticipant对象作为其他所有Entity实体的容器,Publisher、Subscriber、Topic、MultiTopic等均由其产生,用于在实际互联的物理网络上创建虚拟的网络域,进行应用通信间的隔离,通过将DomainParticipant分配到不同的DomainID来实现隔离功能。
- 主题模块
主题模块由下述内容构成:
- TopicDescription
- Topic
- ContentFilteredTopic
- MultiTopic
- TopicListener
- TypeSupport
主题模块的UML模型如图6所示,图中标注了主题模块常用类的基本方法。
图 6 主题模块UML模型示意图
TopicDescription类是主题模块其他类的基类,包括Topic、ContentFilteredTopic、MultiTopic均由其派生,TopicDescription表示发布方和订阅方都与某个数据类型相关联,成员type_name是字符串变量,需要在整个域中唯一。Topic类包含了具体的主题信息,在创建DataWriter和DataReader时,需要指定关联的Topic对象,以便能够通过该Topic进行数据的发布与订阅。
ContentFilteredTopic提供了更为复杂的主题应用,可以基于预定的标准过滤出符合要求的数据,MultiTopic提供了另一种复杂主题应用,将来自多个主题的数据选择并组合到目的数据类型中,通过预定的匹配公式过滤和重新组织需要的数据内容。
TypeSupport接口是抽象接口,应用在使用时需要根据具体的数据类型进行继承和实现,目前版本的DDS提供了工具,用户只需使用IDL定义数据结构,使用该工具自动生产与具体数据类型相关的、能被应用使用的TypeSupport接口。应用使用前,需要将数据类型在本域中进行登记注册,通过register_type动作完成。
- 发布模块
发布模块由下述内容构成:
- Publisher
- DataWriter
- PublisherListener
- DataWriterListener
发布模块的UML模型如图7所示,图中标注了发布模块常用类的基本方法。
图 7 发布模块UML模型示意图
Publisher对象负责实际的发布功能,对应多个DataWriter对象,DataWriter则与每一种Topic相关联,即一个DataWriter对象与一种数据类型进行映射,发布某种数据类型的样本数据时,只能使用该数据类型对应的DataWriter,因此,Publisher包含多个DataWriter,单个Publisher可以发布多种不同类型的数据。
- 订阅模块
订阅模块由下述内容构成:
- Subscriber
- DataReader
- DataSample
- SampleInfo
- SubscriberListener
- DataReaderListener
- ReadCondition
- QueryCondition
订阅模块的UML模型如图8所示,图中标注了订阅模块常用类的基本方法。
图 8 订阅模块UML模型示意图
Subscriber对象负责实际的订阅功能,对应多个DataReader对象,DataReader则与每一种Topic相关联,即一个DataReader对象与一种数据类型进行映射。当有数据到达时或者状态变更(比如有新的匹配实体上线、数据样本丢失、数据拒绝接收等),异步接收模式下,会触发挂接的对应回调函数,通知应用数据到来或者状态变更。订阅某种数据类型的样本数据时,只能使用该数据类型对应的DataReader,因此,Subscriber包含多个DataReader,单个Subscriber可以订阅多种不同类型的数据。
异步通知模式下,每个DataReader在创建阶段或者使能前,通过指定或者调用接口,完成DataReaderListener对象的挂接,这样才能在有事件到来时,以类似中断的形式告知上层应用数据到达。
同步通知模式下,需要配合使用Condition、WaitSet来实现。一个WaitSet允许应用等待一个或多个附加的Condition值变为真(或一直等待直到超时)。应用创建WaitSet,附加一个或多个Condition,然后调用WaitSet的wait()方法。wait()方法会一直阻塞,直到WaitSet关联的一个或多个Condition变为真(当前只能等到一个Condition变为真)。
支持的Qos
DDS的行为通过QoS控制,以适应不同场景下的通信处理需求,每一种实体对象(DomainParticipant, Topic, Publisher, Subscriber, DataWriter, DataReader)都有各自的QoS。表 2列举了目前DDS所涉及的Qos策略,并对其进行简单介绍。
表 2 Qos策略
QoS 策略 | 描述 |
AsynchronousPublisher | 该Qos控制是否使用异步发送。 |
Batch | 该Qos控制如何将数据打包发送。 |
DataWriterMessageMode | 该Qos允许用户控制报文的信息,已减少报文的内容 |
Deadline | 该Qos指定了一个Topic下的每个实例数据发布的最小周期。 |
DestinationOrder | 该Qos控制DataReader接收的来自不同DataWriter的数据样本之间的顺序。 |
DiscoveryConfig | 该Qos用于声明和判断DomainParticipant的存活性。 |
Durability | 该Qos控制DataReader获取DataWriter发送的“历史数据”的与否以及数据来源。 |
EntityFactory | 该Qos控制实体是否自动使能其所创建的子实体。 |
GroupData | 该Qos允许应用向Publisher和Subscriber附加额外的信息。 |
History | 该Qos配置了ZRDDS在本地存储的历史样本的数量。 |
Lifespan | 该Qos控制DataWriter避免发送过期数据给应用。 |
Liveliness | 该Qos指定ZRDDS服务如何判断一个DataWriter是否存活。 |
Log | 该Qos用来设置日志 |
DDSMsgStatisticsInfo | 该Qos允许用户配置报文统计信息的发布信息。 |
Ownership | 该Qos控制ZRDDS服务是否允许多个DataWriter同时更新同一数据实例下的数据对象。 |
OwnershipStrength | 该Qos是Ownership的扩展,当且仅当ownership.kind = EXCLUSIVE_OWNERSHIP_QOS时,ownership_strength. value的取值有意义。 |
Partition | 该Qos决定Publisher和Subscriber创建的DataWriter和DataReader所属的“逻辑分区”,从而控制之间的数据通信。 |
Presentation | 该Qos允许用户指定不同的描述范围 |
DataWriterProtocol | 该Qos提供了控制协议中用户可控制部分的方法。 |
PublishMode | 该Qos决定DataWriter的发送模式是同步还是异步。 |
RapidIOConfig | 该Qos允许用户对RAPIDIO进行配置 |
RapidIOController | 该Qos允许用户指定RAPIDIO的控制器 |
ReaderDataLifeCycle | 该Qos控制DataReader如何处理它所管理的数据实例的样本的生命周期。 |
ReceiverThreadConfig | 该Qos用于配置DomainParticipant的线程数量。 |
Reliability | 该Qos决定ZRDDS的数据传输的可靠性。 |
ResourceLimits | 该Qos决定了ZRDDS所能使用的系统内存。 |
ThreadCoreAffinity | 该Qos用于配置DDS线程与CPU的依赖性。 |
TimeBasedFilter | 该Qos指明一个DataReader在每个最小周期中希望收到最多一包数据。 |
TopicData | 该Qos允许应用向创建的Topic创建附加信息。 |
TransportConfig | 该Qos用于配置发送DomainParticipant消息的目的地址,可以用于发现不同域的DomainParticipant。 |
UserData | 该Qos允许用户向创建的Entity对象(DomainParticipant、DataWriter、DataReader)附加额外信息。 |
WriterDataLifecycle | 该Qos控制了DataWriter如何处理它管理实例的生命周期。 |
DataWriterResourceLimits | 该Qos定义了多种设置可以配置DataWriter如何为内部资源分配和使用物理内存。 |
Qos内部实现设计复杂的实体信息交互,实体的Qos在域发现、端发现阶段会进行合法性、兼容性等判断,对于Qos不兼容、不合法、不匹配的实体,将无法建立正确的连接。因此在配置修改实体的Qos时,应注意下列性质。
- QoS策略的相容性
QoS策略之间会互相影响,当用户设置一个QoS策略时,这个QoS策略的取值可能会与其他QoS策略的取值不相容。使用一组不相容的QoS策略时,set_qos()方法将会失效并返回”RETCODE_INCONSISTENT”。
- QoS策略的可变性
有的QoS策略的值在其关联的实体使能后(一般情况下,实体在创建时就会被自动使能)是不可改变的,而有的QoS策略的值可以在任意时间改变。如果用户试图调用set_qos()方法更改一个不可改变QoS策略(在该QoS策略所关联的实体已使能),将操作失败并返回”RETCODE_IMMUTABLE_POLICY”。
- QoS策略的兼容性
一些QoS策略同时应用于发布端(Publisher, DataWriter)和订阅端时(Subscriber, DataReader)时,需要考虑它们取值的兼容性。要使发布端和订阅端相匹配,需要保证发布端和订阅端的行为互相兼容。
用户如果不熟悉相关Qos的功能和配置,建议使用默认的Qos配置,或查询详细资料使用。
- DomainParticipantFactory默认Qos:
DEFAULT_DOMAINPARTICIPANT_FACTORY_QOS
- DomainParticipant默认Qos:
DEFAULT_DOMAINPARTICIPANT_QOS
- Publisher默认Qos:
DEFAULT_PUBLISHER_QOS
- Subscriber默认Qos:
DEFAULT_SUBSCRIBER_QOS
- Topic默认Qos:
DEFAULT_TOPIC_QOS
- DataWriter默认Qos:
DEFAULT_DATAWRITER_QOS
- DataReader默认Qos:
DEFAULT_DATAREADER_QOS
辅助实体及接口
- Listener实体
Listener以异步方式监视实体的状态变化,例如: DDS发现一个与DataWriter匹配的DataReader、一个新数据到达DataReader等。通过注册的Listener,用户可以实时获取这些信息。实体Listener中的方法与实体关联的状态一一对应。
所有实体类都关联一个特定的Listener,这些Listener会针对其状态(不同类型的实体不同),提供不同方法。在关联的实体的状态发生变化时,这些方法将会由DDS内部的线程调用,回调通知用户。不同实体的Listener的回调函数如表3所示。
表 3 Listener的回调函数
Listeners | 回调函数 | |
DomainParticpants | Topics | on_inconsistent_topic() |
Subscribers | on_data_on_readers() | |
DataReaders | on_data_available() | |
on_liveliness_changed() | ||
on_requested_deadline_missed() | ||
on_requested_incompatible_qos() | ||
on_sample_lost() | ||
on_sample_rejected() | ||
on_subscription_matched() | ||
DataWriters | on_liveliness_lost() | |
on_offered_deadline_missed() | ||
on_offered_incompatible_qos() | ||
on_publication_matched() |
用户可以在创建实体时进行注册Listener,也可以在实体创建后调用实体的set_listener方法注册Listener。用户通过掩码(mask)机制选择Listener监听的状态类型及范围。“mask”是一段二进制码,每一位都代表一个不同的状态。在创建实体时,注册Listener的同时配置“mask”,可以动态配置Listener监听一种或几种指定的状态,如果设置为 STATUS_MASK_NONE ,表示不对任务状态进行监听,如果设置为 STATUS_MASK_ALL ,表示对所有状态进行监听。
Listener类是所有实体的Listener的抽象基类。不同层次的Listeners之间是继承关系,这意味着子Listeners可以使用父Listeners的任意方法。各类Listeners之间的关系如图9所示。
图 9 各类Listeners的继承关系
- Status状态
Status代表一个实体的状态或相关的事件,每个实体关联一系列的代表该实体“通信状态”的状态对象。这些状态被DDS实时监控。状态结构体中包含的数值可以提供更多关于该状态的信息。
状态分为两种类型,如表4所示,每种状态的变化将触发对应实体挂接的Listener回调函数:
- 直接通信状态:除了表明状态是否改变的标识还包含保存当前状态的对应结构体。
- 读通信状态:读通信状态更像一个事件,除了是否发生以外没有其他声明。只有两个状态是读通信状态: DATA_AVAILABLE和DATA_ON_READERS。
表 4 Status状态列表
Entity | Status Name | Meaning |
Topic | INCONSISTENT_TOPIC | 发现同名主题,但 Qos 等特性不一样 |
Subscriber | DATA_ON_READERS | 新的数据到来 |
DataReader | SAMPLE_REJECTED | 一个样本被拒绝接收 |
LIVELINESS_CHANGED | 对端的 DataWriter 存活状态发生变化,变为 “active” 或 “inactive” | |
REQUESTED_DEADLINE_MISSED | 规定的截止时间内未收到对端的通告 | |
REQUESTED_INCOMPATIBLE_QOS | 对端的 DataWriter 的 Qos 与本地 Qos 配置不兼容 | |
DATA_AVAILABLE | 新的数据到来 | |
SAMPLE_LOST | 一个样本数据发生丢失 | |
SUBSCRIPTION_MATCHED | 发现一个 Qos 兼容,且 Topic 相同的 DataWriter | |
DataWriter | LIVELINESS_LOST | DataWriter 在规定时间内未通告其 liveliness 的存在,判定为 “inactive" 状态 |
OFFERED_DEADLINE_MISSED | DataWriter 在规定的时间内未进行 Deadline 宣告 | |
OFFERED_INCOMPATIBLE_QOS | 对端的 DataReader 的 Qos 与本地 Qos 配置不兼容 | |
PUBLICATION_MATCHED | 发现一个 Qos 兼容,且 Topic 相同的 DataReader |
- Condition与WaitSet
区别于Listener提供异步的回调函数,Condition和WaitSet为应用提供了另一种DDS通信状态变化的机制——同步等待机制。
WaitSet允许应用等待一个或多个附加的Condition值变为真。应用创建WaitSet,附加多个Condition,然后调用WaitSet的wait()方法。wait()方法会一直阻塞,直到WaitSet关联的一个或多个Condition变为真。Condition类是所有关联到WaitSet的Condition的基类,主要有以下几种派生类型:
- ReadCondition
由应用通过DataReader创建,但是由DDS触发。提供一种通过指定希望的SampleStateMask、ViewStateMask、以及InstanceStateMask来指定希望等待的数据样本的方法。
- QueryCondition
与ReadCondition类似,QueryCondition 由应用通过DataReader创建,但是由DDS触发。
- GuardCondition
由应用创建,每个GuardCondition具有单一的用户可修改的布尔值”m_triggerValue”。应用可以通过调用set_trigger_value()方法手动的触发GuardCondition。DDS不会触发或清理这类Condition,完全由应用控制。
- StatusCondition
由DDS为每个实体自动创建。当一个实体的使能的状态发生改变时DDS会触发StatusCondition。
3、平台相关模型
平台相关模型(Platform Specific Model,PSM)提供通过IDL来定义应用程序可以进行服务交互的接口。从PIM到PSM的映射是将UML接口和类转为IDL接口,UML的数据类型转为IDL数据结构。通过IDL来映射到C、C++、JAVA、C#等,保证中间件DCPS功能不受编程语言的种类限制,同时屏蔽不同平台数据类型位宽之间的差异性,保证数据大小的统一和DCPS功能正常。
IDL结构体定义
根据用户需求,定义IDL数据类型,有关IDL的语法参看相关标准。下面以一个long型的length和char型的数组为例子,演示如何编写和定义IDL结构体,保存为FooType.idl文件。
编写IDL结构体,实例如下所示:
//@copy // Modification History
//@copy
//@copy // #include “FooTypes.h”
struct FooType
{
long length;//@key
char content[16384];
Bar barfield;//@resolve-name false
};
IDL结构体定义中会包含一些常用的关键标志,比如@key、@copy、@top-level、@resove-name等等。
@key:通过在IDL文件中的一个或多个数据类型后面插入@key标识,声明数据结构的关键字。在dds的code_generator遇到”//@key”时,将采取特殊处理。
@copy:为了在code_generator生成的文件中添加用户定制的信息,可以使用”//@copy”来实现,如果在IDL文件中添加了此标志的行,生成的代码文件中将会拷贝内容到生成的代码文件中。下述标识可以区分生成的目标代码语言类型,根据代码语言类型进行拷贝实现。
表5 IDL注释@copy
@copy-c | Copies code if the language is C or C++ |
@copy-cppcli | Copies code if the language is C++/CLI |
@copy-java | Copies code if the language is Java. |
@copy-ada | Copies code if the language is Ada. |
@resolve-name false:如果IDL结构体定义中嵌套在其他IDL文件中定义的结构体,通过添加此标志可以在编译阶段跳过该类型的解析,让code_generator不去解析对应的数据类型。
@top-level type/false://@top-level false可以指示code_generaor不产生与具体数据类型相关的方法,因为这些数据类型不是最终DCPS层需要使用到的,但由于该数据类型嵌套在top-level数据类型中,仍然会生成该类型的序列化和反序列化函数。通常使用“//@top-level type”是在后续DCPS应用中需要进行Topic主题绑定的数据类型上指定,这样显示声明使得code_generator能够生成该数据类型相关的所有具体的方法,而不仅仅是该数据类型的序列化和反序列化函数。
FooTypeSupport
使用ddsgen_tool生成对应数据类型的TypeSupport文件,使用下述命令:
ddsgen_tool.exe -d 123 -inputIdl FooType.idl -language c++
指定代码语言为C++,生成的文件共有6个,具体包括:
FooType.h
FooType.cpp
FooTypeDataReader.h
FooTypeDataWriter.h
FooTypeTypeSupport.h
FooTypeTypeSupport.cpp
指定代码语言为C,生成的文件共有8个,具体包括:
FooType.h
FooType.c
FooTypeDataReader.h
FooTypeDataReader.c
FooTypeDataWriter.h
FooTypeDataWriter.c
FooTypeTypeSupport.h
FooTypeTypeSupport.c
其中,FooType.h与FooType.c文件定义了FooType结构体的具体形式,以及FooTypeSeq结构及FooTypeSeq的相关函数方法,并实现有关FooType的一些辅助函数,包括FooType的样本创建、初始化、拷贝、序列化、反序列化、哈希值等等。
FooTypeTypeSupport.h/ FooTypeTypeSupport.cpp文件定义了包括数据类型注册、注销、获取hash值等函数。
FooTypeDataWriter.h/FooTypeDataWriter.cpp文件定义了该具体数据类型实例化的DataWriter的一些方法,FooTypeDataReader.h /FooTypeDataReader.cpp文件定义了该具体数据类型实例化的DataReader的一些方法。