# 介绍

# 概述

  • uDDS(platforuDDS)是由南京磐优自主研发的面向未来计算机网络应用系统需求的新一代高性能数据交互解决方案。
  • 该解决方案由一套逻辑相互关联、功能各有侧重的中间件及工具软件组成,构建在分布式系统网络化硬件平台上,面向分布式系统应用领域需求、系统集成特点,结合公司多年来在海军舰艇作战系统、电子信息系统、武器系统等领域系统集成的丰富经验,为用户提供了高效、可靠的开发/部署/集成大规模分布式实时系统的手段。

# 说明文档组成

# 版本

目标运行OS 开发环境 开发方式 开发工具
VxWorks5.5 Windows XP/7/10 dkm Workbench3.2
VxWorks6.8 Windows XP/7/10 RTP/dkm Workbench3.2
VxWorks6.9 Windows XP/7/10 RTP/dkm Workbench3.2
Windows XP/7/10 Windows XP/7/10 Visual Studio 2008~2019
Linux主要发行版 Linux主要发行版 GCC

# 安装

# 什么是DDS和uDDS

# DDS

数据分发服务(Data Distribution Service)是OMG的有关分布式实时系统中数据分发的一个规范。该规范标准化了分布式实时系统中数据发布、传递和接受的接口和行为,定义了以数据为中心的发布/订阅Data-Centric Publish-Subscribe)机制,提供了一个与平台无关的数据模型。

# uDDS

uDDS基于OMG组织 (opens new window)制订的DDS1.4规范 (opens new window),使用订阅/发布机制进行应用功能软件间的信息传输通道组织和信息分发,使应用无需关心信息的来源和去向等信息传输细节,以统一的信息交互接口实现按需信息分发、按需信息获取,根据不同的系统信息流设计模式和要求,提供质量保证策略接口,保证高质量的实时信息传输。

以实时数据分发服务为核心,uDDS还提供了系统状态监视数据记录辅助开发组件检测等工具套件,为系统提供实时、高效、灵活的数据交互手段,并在应用系统设计、开发、调试、运行的各个环节提供完整的支撑服务。

# uDDS的特性

# 完善的开发环境

uDDS为分布式系统的开发者提供了完整的开发环境。用户在应用程序开发阶段可使用IDL语言编译器及辅助开发工具实现中间件相关代码的便捷嵌入;在系统集成阶段可使用应用程序检测工具对程序进行预检测,以降低集成测试的故障发生概率和定位成本;系统实际运行阶段可使用系统监控、数据记录工具来获取系统的运行状态,并能够对数据进行离线分析。使用uDDS提供的完整开发环境,可以极大程度地降低分布式应用系统设计、实现、测试和故障定位的复杂性。

# 高性能数据通信

uDDS通过高效运行的核心代码和灵活多样的QoS策略,保证应用系统在苛刻的应用场景下能够实现良好的数据交互。

# 应用数据过滤

uDDS提供多种应用数据过滤方式。在应用系统中进行一对多的数据传输时,不同的订阅者对同一主题的数据内容、数据频率需求不尽相同。针对这一应用需求,uDDS可根据用户配置,自动过滤掉订阅者不关心的数据,以减少应用程序的数据处理工作。

# 可靠性

uDDS为大规模应用系统中的应用程序提供P2P的信息交互服务,在系统中不存在集中式的代理或服务器进程,从而保证整个系统服务不存在单点故障的风险。

# 独立升级和可移植

uDDS提供多个操作系统的版本,均采用动态库的形式提供应用程序使用,并为不同操作系统下的应用程序提供统一的服务调用接口。应用系统和uDDS可分别进行独立升级,且应用系统在不同操作系统上移植时几乎无需针对uDDS相关的代码进行改动。

# 扩展性

uDDS使用“订阅/发布”机制进行数据交互,建立全局的虚拟数据空间,在通信层面将应用逻辑与节点的物理信息解耦合。应用程序可根据需要在系统中的任意节点上部署或迁移,uDDS均能够自动完成发现过程,保证应用程序实现原有的通信功能。当系统由于链路故障或人为需要而物理上被划分成多个子系统时,各子系统内仍能够按照原有的交互关系进行通信;当多个子系统存在物理连接时,也同样能够完成自动化集成。

# QoS策略丰富

uDDS提供丰富的QoS策略。QoS策略能够独立或组合使用,以满足用户不同的应用场景下对通信质量灵活而复杂的需求。

# IDL至C++的映射

uDDS符合OMG IDL/C++语言映射规范。有关通过idlc编译程序实现的当前uDDS IDL至C++语言的映射的汇总,请参阅IDL-C++映射。对于每个IDL构造,都存在描述相应的C++构造以及代码示例的小节。 有关映射规范的详情,请参阅OMG IDL/C++语言映射规范 (opens new window)

# 完善的开发工具套件

uDDS作为一套成熟的高性能数据交互解决方案,由一套逻辑相互关联、功能各有侧重的中间件及工具软件组成。其中,与用户开发相关的工具主要包括:

  • uDDS数据定义文件编译工具(uDDS IDLC)
  • uDDS数据交互中间件(uDDS)
  • uDDS辅助开发工具(uDDS Developer)

# uDDS数据定义文件编译工具(uDDS IDLC)

uDDS数据交互中间件支持用户通过IDL文件格式定义用户数据类型,以便在数据收发过程中对应用数据进行封装和解析。在进行应用软件开发之前,请使用IDL文件编译器(idlc.exe)对IDL文件进行编译,将生成的C++代码文件添加到应用代码工程中(详见进阶开发)。

# uDDS数据交互中间件(uDDS)

uDDS数据交互中间件以动态库的形式提供应用软件调用,为分布式实时系统提供实时的数据分发服务。为提高应用灵活性,中间件的部分使用参数在配置文件(udds.conf)中进行设置,用户可使用文本编辑器对配置文件进行修改(详见进阶开发)。

# uDDS辅助开发工具(uDDS Developer)

uDDS辅助开发工具(uDDS Developer)以开发环境插件的形式,辅助用户使用uDDS进行应用程序开发(详见辅助开发工具使用说明),包括:

  • 为用户提供界面式的应用程序配置界面,用户可设计应用程序所需的主题数据结构及发布、订阅关系
  • 根据用户配置,自动生成接口代码并添加到应用程序工程中
  • 为应用程序自动设置开发环境所需的配置参数
  • 自动生成配试程序,实现对应用程序通信功能的及时测试

# 兼容性

uDDS兼容以下规范:

详情参考OMG组织 (opens new window)

# 开发实例应用

本节使用实例来描述创建 C++ 下的实时分布式应用程序的开发过程。该实例应用的C++ 代码可以在uDDS软件包安装完成后所在位置的demos目录下找到。

# 开发过程

当您使用uDDS开发分布式应用时,您必须首先确定应用程序需要进行发布/订阅的数据类型。以下是开发该实例所采取的步骤的概要描述:

1 使用接口定义语言(IDL)为每种数据类型编写说明;

2 使用 IDL 编译器生成用户代码;

3 将生成的代码添加到应用软件的开发工程中;

4 设置必须的应用软件开发工程属性;

5 为应用软件编辑对应的配置文件;

6 编写应用软件功能代码。

# 步骤一:定义数据类型

使用 uDDS创建应用的第一步是使用 OMG 的 接口定义语言 (IDL)说明您需要进行发布/订阅的数据类型,然后您可以使用idlc 编译器生成辅助开发代码。数据类型的定义形式为IDL文件,用户可以创建一个文本文件,然后将扩展名修改为“.idl”,并使用文本编辑器进行数据类型定义(语法与C/C++基本相同,支持的数据类型见程序员参考)。 例如需要创建的IDL文件名为UserDataType.idl,内容见下图 用户自定义数据类型idl文件示例

# 步骤二:生成辅助文件

在安装目录下的IDL-tools文件夹找到名为idlc-XX.exe的IDL编译器,名字中的XX为对应操作系统版本,例如idlc-windows.exe或者idlc-vxworks.exe或者idlc-kylin.exe,请注意一定要使用对应的操作系统版本(该文件夹中其他文件为编译器的依赖库,请确保与exe放在同一目录下)。见下图。

用户自定义数据类型idl文件示例

随后,使用控制台进入IDL-tools目录并执行idlc程序,对用户定义的idl文件进行编译。例如IDL编译器名称为idlc.exe,用户定义的idl文件名称为UserDataType.idl,则编译过程见下图。

使用idlc编译用户自定义数据类型文件示例

如果输出结果为用户编译的idl文件名称,如上图所示,则说明编译成功,编译器将在IDL-tools目录下产生十个文件,见下图。

使用idlc编译用户自定义数据类型文件示例

这十个文件的主要内容如下:

  • DDS_API.h:用户接口定义
  • IDL_Agent.h:使用uDDS库需要头文件及类
  • IDL_DataReader.h:UserDataType数据类型订阅接口定义
  • IDL_DataReader.cpp:UserDataType数据类型订阅接口实现
  • IDL_DataWriter.h:UserDataType数据类型发布接口定义
  • IDL_DataWriter.cpp:UserDataType数据类型发布接口实现
  • IDL_TypeSupport.h:UserDataType数据类型实现需要头文件及类
  • IDL_UserDataType.h:UserDataType数据类型的定义
  • IDL_UserDataType.cpp:UserDataType数据类型的函数实现
  • IDL_UserDataTypeTC.h:UserDataType数据类型的TypeCode方法

注意

1、使用系统内置类型_DDS_STRING时,首先需要使用IDL编译器生成文件,否则无法使用;

2、如果用户在Visual Studio中创建工程时选择使用预编译头,需要在生成的IDL文件中的三个cpp文件中添加 #include "stdafx.h"

# 步骤三(1):应用软件及环境配置(Linux-WorkBench)

适用于Windows-VisualStudio的配置点此查看

适用于中标麒麟-Eclipse的配置点此查看

使用步骤二生成的辅助代码进行用户程序开发,新建工程VxWorks Real Time Process Project将上面生成的十个文件添加进来(导入方法:鼠标右击工程->Import->General->File System->Next->选择目录及文件->Finish),并分别添加发布端/订阅端示例程序。见下图。

使用idlc编译用户自定义数据类型文件示例

配置 Build Support and Specs 见下图

配置 Build Support and Specs

将uDDS的头文件路径添加进工程,见下图。(请注意,下图只是说明要修改Workbench中的哪个属性项,具体路径以在您电脑上的真实情况为准)

添加uDDS动态头文件路径

在项目属性Properties界面Build Tools子界面中,选择Build tool为“C++-Compiler”,随后将“-DVXWORKS”添加进命令参数(注意-DVXWORKS的短横线前面有个空格),见下图。

添加uDDS动态头文件路径

在同一界面中,C++-Compiler改为Linker,点击Tool Flags,并在弹出的界面中勾选Create a Dynamic executable前面的复选框,见下图:

添加uDDS动态头文件路径

添加uDDS动态头文件路径

将动态库的路径添加进来,见下图:

添加uDDS动态库路径

将配置文件udds.xml放置到预定路径下。该路径可以任意设置,开发者只需要在上面步骤二中生成的DDS_API.h头文件内找到对应的位置修改宏定义“uDDS_CONFIG_FILE_PATH”即可。注意务必确保该路径在目标机运行的时候能够访问到,一般放在目标机硬盘内(下方示例1),或开发机FTP目录下(下方示例2)。

示例1:

#define UDDS_CONFIG_FILE_PATH "/ata0a/udds.xml"
1

示例2:

#define UDDS_CONFIG_FILE_PATH "host:udds.xml"
1

# 步骤三(2): 应用软件及环境配置(WindowsXP/7-Microsoft Visual Studio)

回到上一步

使用步骤二生成的辅助代码进行用户程序开发,新建工程将上面生成的十个文件添加进来,并分别添加发布端/订阅端示例程序。见下图。

将uDDS的头文件路径添加进工程,见下图。(请注意,下图只是说明要修改VS中的哪个属性项,具体路径以在您电脑上的真实情况为准)

将uDDS的lib路径添加进来,并添加lib文件名称,见下图。(请注意,下图只是说明要修改VS中的哪个属性项,具体路径以在您电脑上的真实情况为准)

再将sqlite3.dll,uDDS.dll和common文件夹拷到应用软件可执行程序的同一目录下(一般是Debug目录),并将common文件夹同时拷贝到应用软件工程(注意是.vcproj文件,而不是.sln文件)目录下。见下图。 说明:Windows版本的uDDS核心库默认C:\uDDS\common读取udds.xml以获取运行配置。Visual Studio在执行“F5”或“ctrl+F5”时,“当前目录”是.vcproj文件所在的目录,所以需要在此目录下也拷贝一份配置文件以便调试运行。否则将只支持在Debug目录下双击exe运行应用软件。

继续下一步

# 步骤三(3): 应用软件及环境配置(NeoKylin-Eclipse)

回到上一步

使用步骤二生成的辅助代码进行用户程序开发,新建工程将上面生成的十个文件添加进来,并分别添加发布端/订阅端示例程序。见下图

添加预处理KYLIN到工程中,如下图。

将uDDS的头文件路径添加进工程,见下图。(请注意,下图只是说明要修改Eclipse中的哪个属性项,具体路径以在您电脑上的真实情况为准)

将uDDS的动态库名称添加到项目属性,见下图。

将动态库搜索路径添加到工程属性,目的是在程序运行时可以找到动态库(注意这与Windows平台下下自动寻找当前路径下的动态库不同),见下图。(请注意,下图只是说明要修改Eclipse中的哪个属性项,具体路径以在您电脑上的真实情况为准)

添加时注意,需要加上-R的前缀,之后是动态库的路径。如下图

还有其他两种方法可以使应用软件运行时能够搜索到动态库:

1)修改系统的默认搜索路径,类似添加环境变量LD_LIBRARY_PATH;

2)将DDS动态库放入/lib或者/usr/lib下。

将common文件夹拷到应用软件可执行程序的同一目录下(一般是Debug目录),并将common文件夹同时拷贝到应用软件调试目录下(注意如果使用Eclipse调试的方式运行,可执行程序的路径是Debug文件夹)。

继续下一步

# 步骤四:配置文件设置

根据用户的实际需求,进行配置文件的适应性修改,一般需要确认以下部分:

IP地址配置

其中,红色圈的部分按默认值保持不变,绿色圈的部分请按照实际运行机器的IP地址和子网广播地址进行配置。

说明:关于配置文件的具体说明请参见附件1 (opens new window)。更多情况下的配置使用方法,请参见进阶开发

# 步骤五:应用软件开发

根据用户的实际需求,进行应用软件代码的开发。开发人员可参考以下示例程序。关于示例程序中uDDS相关函数的具体定义,请参见接口设计

注意:软件开发完成后,需将

  • udds.so
  • libc.so.1
  • DDSDemo(DemoPublisher/DemoSubscriber).vxe

放在同一目录(libc.so.1位于installDir/vxworks-6.x/target/usr/root/cpuTool/bin或者installDir/vxworks-6.x/target /lib/usr/root/cpuTool/bin),

例如:

D:/WindRiver6.8.3/vxworks-6.8/target/lib/usr/root/PENTIUM4gnu/bin
1

需要确认目标机VxWorks镜像中包含以下组件:

  • C++
  • POSIX
  • Clocks

# 接口设计

# 与应用构件的接口

uDDS以动态库的形式提供应用软件调用,在进行应用软件开发时,程序员可参阅下文中的说明,通过编程调用相应的功能。

表 1 与应用构件的接口

接口功能 接口函数
加入数据域 DomainInit
创建发布者 CreateDataWriter
删除发布者 DeleteDataWriter
创建订阅者 CreateDataReader
删除订阅者 DeleteDataReader
退出数据域 DomainRelease
发送数据 Write
接收数据 Read_Next_Sample

# 加入数据域接口

DomainInit

  • 函数原型
_RETURNCODE_T  DomainInit( _DOMAINID_T  DomainId, char* pCompName );
1
  • 功能简介

初始化节点,使节点加入数据域。节点加入数据域后,才能够在该数据域上使用发布订阅服务。该操作返回操作索引标志码。

  • 调用场景 建议在应用软件初始化阶段调用,一次调用后,即可在本次软件执行过程中长期使用中间件进行信息分发,直到调用DomainRelease。 每个不同的域均需初始化一次。

  • 输入参数

_DOMAINID_T DomainId [in]:_DOMAINID_T结构体变量,表示本应用软件要加入的数据域ID。

const char* CompName[in]:应用软件在系统内的唯一名称,一般为应用名。

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回0,否则返回错误索引码。

# 创建发布者接口

CreateDataWriter

  • 函数原型
DataWriter* CreateDataWriter(	 const char *pComponentName,
                             _DOMAINID_T DomainId,
                             const char *pTopic_name,
                             const char *pType_name,
                             DataWriterListener *pListener,
                             const _DATA_WRITER_QOS *pQos);
1
2
3
4
5
6
  • 功能简介

本接口主要完成发布者的创建,包括本地资源的申请和初始化,QoS策略配置,以及通知数据域中所有的订阅者。该操作返回创建的发布者实体指针。

  • 调用场景

建议在应用软件初始化阶段调用,针对每个主题调用一次本接口,即可在软件本次运行中,根据逻辑功能需要多次发送该主题的实例数据,直到调用DeleteDataWriter。

  • 输入参数

const char* pComponentName[in]:需要进行数据发布的应用软件名称

_DOMAINID_T DomainId[in]:数据发布所在数据域ID

const char* pTopic_name[in]:需要发布的数据主题名称

const char* pType_name[in]:需要发布的数据主题类型

DataWriterListener* pListener[in]:发布者对应的监听器指针

const _DATA_WRITER_QOS* pQos[in]:发布者绑定的QoS策略

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回创建成功的发布者对象的指针,否则返回空指针NULL。

# 删除发布者接口

DeleteDataWriter

  • 函数原型
_RETURNCODE_T  DeleteDataWriter( DataWriter *pDataWriter )                             
1
  • 功能简介

取消对某个主题的发布,删除数据发送实体,该主题将不能再被该实体发送。

  • 调用场景

建议在应用软件停止运行前调用,针对每个主题调用一次本接口,以回收该主题发布时分配的计算机资源。本接口被调用后,应用软件将不能再次发送对应的主题实例数据。除非再次调用CreateDataWriter。

  • 输入参数

DataWriter* pDataWriter[in]:需要删除的发布者实体指针

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回0,否则返回错误索引码。

# 创建订阅者接口

CreateDataReader

  • 函数原型
DataReader* CreateDataReader(	 const char *pComponentName,
                             _DOMAINID_T DomainId,
                             const char *pTopic_name,
                             const char *pType_name,
                             DataReaderListener *pListener,
                             const _DATA_READER_QOS *pQos);
1
2
3
4
5
6
  • 功能简介

本接口主要完成订阅者的创建,包括本地资源的申请和初始化,QoS策略配置,以及通知数据域中所有的发布者。该操作返回创建的订阅者实体指针。

  • 调用场景

建议在应用软件初始化阶段调用,针对每个主题调用一次本接口,即可在软件本次运行中,实时获取中间件接收到的该主题的实例数据,直到调用DeleteDataReader。

  • 输入参数

const char* pComponentName[in]:需要进行数据订阅的应用软件名称

_DOMAINID_T DomainId[in]:数据订阅所在数据域ID

const char* pTopic_name[in]:需要订阅的数据主题名称

const char* pType_name[in]:需要订阅的数据主题类型

DataReaderListener* pListener[in]:订阅者对应的监听器指针

const _DATA_READER_QOS* pQos[in]:订阅者绑定的QoS策略

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回创建成功的订阅者对象的指针,否则返回空指针NULL。

# 删除订阅者接口

DeleteDataReader

  • 函数原型
_RETURNCODE_T  DeleteDataReader( DataWriter *pDataReader)    
1
  • 功能简介

取消对某个主题的订阅。删除数据接收实体,该主题将不能再被该实体接收。

  • 调用场景

建议在应用软件停止运行前调用,针对每个主题调用一次本接口,以回收该主题订阅时分配的计算机资源。本接口被调用后,应用软件将不能再次接收对应的主题实例数据。除非再次调用CreateDataReader。

  • 输入参数

DataReader* pDataReader[in]:需要删除的订阅者实体指针

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回0,否则返回错误索引码。

# 退出数据域接口

DomainRelease

  • 函数原型

_RETURNCODE_T DomainRelease(_DOMAINID_T DomainId)

  • 功能简介

删除应用软件在某个数据域内的实例,使应用软件退出某一数据域,结束在该数据域内的发布订阅行为。

  • 调用场景

建议在应用软件停止运行前调用,针对软件加入的每个数据域调用一次本接口(确保在本应用软件对该数据域内的所有发布者实例调用DeleteDataWriter,所有订阅者实例调用DeleteDataReader后执行),以回收应用软件为了在该数据域内进行订阅发布所分配的计算机资源。本接口被调用后,应用软件将不能在对应的数据域内使用数据分发服务,除非再次调用DomainInit。

  • 输入参数

_DOMAINID_T DomainId[in]:需要退出的数据域ID

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回0,否则返回错误索引码。

# 发送数据接口

Write

  • 函数原型
_RETURNCODE_T XXDataWriter::Write(UserDefineType Data)
1
  • 功能简介

发布者在成功发布一个主题后,在对应的数据域内进行主题实例数据的发送。

  • 调用场景

应用软件根据自己的应用逻辑,需要发送数据时调用。调用一次本接口将发送一次实例数据。有SOCKET开发经验的用户可理解为调用了一次Sendto。

  • 输入参数

UserDataType data[in]:用户自定义数据类型的实例数据(或者内置数据类型的实例数据)

  • 输出参数

无。

  • 返回值

该函数若成功执行则返回0,否则返回错误索引码。

# 接收数据接口

Read_Next_Sample

  • 函数原型
_RETURNCODE_T XXDataReader::Read_Next_Sample(UserDefineType Data)
1
  • 功能简介

订阅者在成功订阅一个主题后,通过本接口从数据域中接收该主题的实例数据,中间件将接收到的数据存储到用户自定义数据类型(或者内置数据类型)变量中。

  • 调用场景

在对应主题的回调函数中调用,可参见《开发者指南》。

  • 输入参数

无。

  • 输出参数

UserDataType &data[out]:用户自定义数据类型的实例数据(或者内置数据类型的实例数据)。

  • 返回值

该函数若成功执行则返回0,否则返回错误索引码。

# 发布应用程序流程

►加入数据域

应用程序加入数据域,为创建发布者主题,进行数据发布准备,需要判断函数返回值。

	_RETURNCODE_T ret;

    //应用组件名为DDSTest加入到数据域的初始化过程
	ret = DomainInit(domainId,compName);

	if (ret != RETURNCODE_OK) //出错处理
	{
		cout<<"Domain: "<<domainId <<" Init failed"<<endl;
		return 0;
	}
1
2
3
4
5
6
7
8
9
10

►声明创建发布主题参数

声明CreateDataWriter()函数所需的参数。_DATA_WIRTER_QOS对象用于设置发布者的qos策略,可通过设置其中值来配置发布者的策略。

    //应用组件QoS设置
	_DATA_WRITER_QOS *qos = new _DATA_WRITER_QOS();
	qos->Reliability.Kind = BEST_EFFORT;
1
2
3

►创建发布主题

调用CreateDataWriter()函数创建发布者,并通过安全类型转换成对应结构体的发布者,例如下方代码中将DataWriter*类型转换成数据类型myData对应的myDataDataWriter*类型。注意此处得类型转换需要和CreateDataWriter()中第四个参数对应。

//创建发布者
	DataWriter * dataWriter = NULL;
	dataWriter = CreateDataWriter(compName,domainId, "00001" , "myData", NULL, qos);	
	if (dataWriter == NULL) 
	{
		cout<<"Create Data Writer failed"<<endl;
		DomainRelease(domainId);
		return 0;
	}
	//安全的类型转换
	myDataDataWriter *myDataTypeDataWriter = NULL;
	myDataTypeDataWriter = myDataDataWriter::Narrow(dataWriter);
	if (myDataTypeDataWriter == NULL) 
	{
		cout<<"myDataDataWriter Narrow failed"<<endl;
		DeleteDataWriter(dataWriter);
		DomainRelease(domainId);
		return 0;
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

►发送数据

myData data;
data.color = new char[30];
	cout<<"Please enter the point and color you want to send,such as 3 4 red"<<endl;
	while(1)
	{
		cin>>data.x>>data.y>>data.color;	
		//将用户输入的数据调用Write接口发送
		ret = myDataTypeDataWriter->Write(data);
		//返回发送结果
		if (ret != RETURNCODE_OK) 
		{
			cout <<"Write error: " << ret << endl;
		}
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

创建数据对象并进行赋值,并调用write()接口发送数据。

►删除发布者,退出数据域

    //安全删除dataWriter
	DeleteDataWriter (dataWriters);
	//安全退出参与数据域
	DomainRelease(domainId);
1
2
3
4

调用接口DeleteDataWriter()删除发布者,DomainRelease()退出数据域。

# 订阅应用程序流程

►继承DataReaderListener类

每个订阅者需要创建一个Listener类型,继承DataReaderLisenter类,重写On_Data_Available()函数,CreateDataReader()需要将Lisenter类的指针传入内部,uDDS内部在收到数据后,通过Lisenter指针调用On_Data_Available()将数据提交给用户。

class HelloListener : public DataReaderListener 
{
public:
	HelloListener(long num)
	{
		receiveCount = num;
	}
	_RETURNCODE_T On_Data_Available(DataReader* dataReader)
{
	_RETURNCODE_T ret = RETURNCODE_OK;
//安全数据类型转换,将DataReader* 转换为对应的数据类型的指针 
	myDataDataReader* myDataReader = NULL;
	myDataReader = myDataDataReader::Narrow(dataReader);

	if (myDataReader == NULL) 
	{
		cout << "Narrow failed at On_Data_Available of HelloListener" << endl;
		return -1;
	}
	myData data;
	ret = myDataReader->Read_Next_Sample(data);
	if (ret != RETURNCODE_OK) 
	{
		return -1;}
	cout<<"Received "<< data.x<<" "<<data.y<<" "<<data.color<<endl;
	return RETURNCODE_OK;
}
	long receiveCount;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

►加入数据域

应用程序加入数据域,为创建发布者主题,进行数据发布准备,需要判断函数返回值。

    _RETURNCODE_T ret;

	//应用组件名为DDSTest加入到数据域的初始化过程
	ret = DomainInit(domainId,compName);

	if (ret != RETURNCODE_OK) //出错处理
	{
		cout<<"Domain: "<<domainId <<" Init failed"<<endl;
		return 0;
	}
1
2
3
4
5
6
7
8
9
10

►声明创建订阅者参数

声明CreateDataReader ()函数所需的参数。_DATA_READER _QOS对象用于设置订阅者的qos策略,可通过设置其中值来配置订阅者的策略。 创建Listener对象用于接收数据。

    //应用组件QoS设置
	_DATA_READER_QOS *qos = new _DATA_READER_QOS();
	qos->Reliability.Kind = BEST_EFFORT;
	HelloListener *listener = new HelloListener(0);
1
2
3
4

►创建订阅者

调用CreateDataReader ()函数创建订阅者。

    //创建发布者
	DataReader * dataReader = NULL;
	dataReader = CreateDataReader(compName,domainId, "00001" , "myData", listener, qos);	
	if (dataReader == NULL) 
	{
		cout<<"Create Data Reader failed"<<endl;
		DomainRelease(domainId);
		return 0;
	}
1
2
3
4
5
6
7
8
9

►接收数据

接收数据是在listener类中的On_Data_Available()函数中实现。

►删除订阅者,退出数据域

调用接口DeleteDataReader()删除订阅者,DomainRelease()退出数据域。

    //安全删除dataWriter
	DeleteDataReader (dataReader);
	//安全退出参与数据域
	DomainRelease(domainId);
1
2
3
4

►配置文件接口

系统在进行部署运行时,需要对应用软件进行配置参数的预定义。每个应用软件的配置参数均存储在名为“udds.xml”的配置文件中,用于规定应用软件所需的QoS策略等参数信息。未提到的配置项请勿修改,常用的主要配置项如下:

  • IP地址配置项

使用uDDS前,必须在udds.xml中配置IP地址。 windows下修改将LocalIp值修改为使用的本机IP,如果该Ip所属网络适配器未连接网线,可能会导致DomainInit失败。

<LocalIp>198.1.106.55</LocalIp>
1

Linux下修改EthName值为使用的网卡名,如:eth。

<HostIP EthName="eth">0.0.0.0</HostIP>
1
  • 数据发送缓冲区配置项

若无必要,无需修改。当以极高的频率发送数据时,发送缓冲区可能不够用,可通过CacheLength调整缓冲区大小。

<CacheLength>1001</CacheLength>
1
  • 组播发现配置项

若无必要,无需修改。uDDS默认使用组播进行发现匹配,所有进行通信的uDDS该配置项需要一致。

UseMulticastDiscovery: 1 使用组播发现 0 不使用组播发现

MultiAddr 使用组播发现时的组播地址。

<MulticastDiscovery UseMulticastDiscovery="1">
<MultiAddr>239.255.0.1</MultiAddr>
</MulticastDiscovery>
1
2
3
  • 组播通信配置项

若无必要,无需修改。设置用户数据的传输方式。

UseMulticast: 1 设置使用组播通信 0 设置使用单播通信

MultiAddrLow: 设置组播通信的组播地址

MultiAddrNumber:使用组播地址的数量,例如10,即为使用239.3.3.3~239.3.3.12共十个组播地址

<Multicast UseMulticast="1">
<MultiAddrLow>239.3.3.3</MultiAddrLow>
<MultiAddrNumber>1</MultiAddrNumber>
</Multicast>
1
2
3
4
  • 自动检测节点失效时间

默认自动检测节点失效时间为30s,每个节点隔30s广播一次本节点的消息。其他节点判断,若当前时间减去上次收到广播报文的时间超过30*3s,则可认为这个节点已经失效,将这个节点及其创建的所有发布/订阅者从匹配列表中删除。

<LeaseDuration>
<PeriodSecond>30</PeriodSecond>
<PeriodNanosec>0</PeriodNanosec>
</LeaseDuration>
1
2
3
4

# IDL-C++映射

# 基本数据类型

下表概括了由接口定义语言(IDL)提供的基本数据类型,基本类型映射如下。

IDL C++
short short
long long
unsigned short unsigned short
unsigned long unsigned long
float float
double double
char char
boolean unsigned char
octet unsigned char

# 字符串

下面说明了对字符串的映射:

//IDL
typedef string<10> stringTen; // 定界字符串
typedef string stringInf;     // 不定界字符串
1
2
3

相应的C++映射代码为:

//C++
typedef char* stringTen;
typedef const char* stringTen_IDL_const;
typedef char* stringInf;
typedef const char* stringInf_IDL_const;
1
2
3
4
5

# 常量和枚举类型的映射

文件级的全局的IDL常量类型将映射成文件级的C++的静态常量类型,例如:

//IDL
const long MaxLen = 4;	

//C++
static const long MaxLen = 4;
1
2
3
4
5

IDL枚举类型将映射成C++中对应的枚举类型。例如:

//IDL
enum Color { blue,green};
1
2

将映射成:

//C++
enum Color { blue, green, IDL_ENUM_Color = 99999 };
1
2

►类型(typedef)定义 IDL中的类型定义直接对应于C++中的类型定义。例如,如下定义IDL的类型定义:

//IDL
typedef string myString;
1
2

将映射成下列C++的类型定义:

//C++
typedef char* myString;
typedef const char* myString_IDL_const;
1
2
3

# 结构类型的映射

IDL结构类型直接映射为C++结构类型。每个IDL结构的成员映射为C++结构的相应成员。生成的结构包含构造函数和析构函数。例如:

//IDL
struct Astruct{
long l;
float f;
};
1
2
3
4
5

映射为:

//C++
struct Astruct {
	Astruct(){}
	Astruct(const Astruct  &IDL_s);
	~Astruct(){}
	long l;
	float f;
	//其他一些这里不关注的函数
};
1
2
3
4
5
6
7
8
9

# 联合类型的映射

IDL的联合类型将映射成C++的类。例如下列IDL声明:

//IDL
typedef long vector[100];


struct S{
   string str;
};
union U switch(long){
	case 1:float 	f;
	case 2:vector	v;
	case 3:string	s;
	default:S	st;
};
1
2
3
4
5
6
7
8
9
10
11
12
13

将映射成下列C++结构:

#ifndef U_defined
#define U_defined
class U {
private:
	long __d;
	union{
		float _f_;
		vector_slice* _v_;
		char** _s_;
		S*  _st_;
 	};
unsigned char isSet;
public:
	U();
	U(const U &IDL_s);
	~U();
	U& operator= (const U &IDL_s);

  	long _d() const {return __d;}
	float f () const{return _f_; }
	void f(float IDL_member);
	vector_slice* v () const { return (_v_); }
	void v(vector IDL_member);
	const char * s () const { return (*_s_); }
	S& st () {return (*_st_); }
	const S& st () const {return (*_st_); }
	void st(const S& IDL_member);
	void Marshal(CDR *cdr) const ;
  	void UnMarshal(CDR *cdr);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 序列类型的映射

IDL中的序列类型映射为:

  • 一个可以像数组一样使用的,具有当前长度和最大长度的C++类。
  • 一个序列的_var类型。

# 无界序列的映射

考虑下列IDL描述

//IDL
//无界序列
typedef sequence <long> unbounded;
1
2
3

IDL编译器将产生如下C++定义:

//C++
class _IDL_SEQUENCE_long {
	unsigned long _maximum;
	unsigned long _length;
	unsigned long _release;
	long* _buffer;
public:
	_IDL_SEQUENCE_long();		(1)//缺省构造函数不分配空间
	_IDL_SEQUENCE_long(const _IDL_SEQUENCE_long &IDL_s); (2)
	_IDL_SEQUENCE_long(unsigned long IDL_max); (3)
	_IDL_SEQUENCE_long(unsigned long max, unsigned long length, 
        long* data, unsigned char release = 0); 		(4)
	~_IDL_SEQUENCE_long();						(5)
	_IDL_SEQUENCE_long& operator= (const _IDL_SEQUENCE_long &IDL_s);(6)
    
        long& operator [](unsigned long IDL_i){
            if(IDL_i >= _length) throw RETURNCODE_NO_MEMORY;
            return _buffer[IDL_i];
 	}												(7)
  	long& operator [](unsigned long IDL_i) const{
            if(IDL_i >= _length) throw RETURNCODE_NO_MEMORY;
            return _buffer[IDL_i];
  	}												(8)
  	static long* allocbuf(unsigned long nelems){
            return (new long[nelems]);
  	}												(9)
  	static void freebuf(long* data){
            if(data) delete [] data;
  	}												(10)
  	unsigned long maximum() const {return _maximum; }(11)
  	unsigned long length() const {return _length; }	(12)
  	void length(unsigned long len);		        (13)
	//其他函数};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

构造函数、析构函数和赋值操作

缺省构造函数(1)将序列长度和最大长度都设置为0。

拷贝构造函数(2)创建一个长度及最大长度都和所给序列相同的新序列,并复制其每个元素。

构造函数(3)分配新的存储区。

构造函数(4)允许序列的存储区在序列的定义之外分配。通常序列将管理它们的存储区。然而,改构造函数允许由参数release来决定其存储区的所有权:0(false)意味着调用者管理该存储区,而1(true)意味着序列将管理该存储区。

序列缓冲区管理:allocbuf()和freebuf()

静态成员函数allocbuf()(9)和freebuf()(10)控制构造函数(4)的序列缓冲区内存分配。

函数allocbuf()动态的分配单元的缓冲区,可以通过data参数传递给构造函数(4),如果不能分配时将返回空指针。

函数freebuf()释放由allocbuf()分配的缓冲区。它忽略传给它的空指针。对于数组序列类型,allocbuf()的返回类型和freebuf()的参数是数组切片的指针。

当release标记置为true而序列元素的类型是string或对象引用时,序列将在释放缓冲区前单独释放每个元素。

其他函数

函数maximum()(11)返回当前可用的缓冲区空间总量。这让程序可以知道它可以往无界数组内插入多少项而不至于引起重新分配空间。

重载的操作符 operator(7,8)返回给定下标的序列元素。不能访问或修改超过当前序列长度的元素。在operator对序列使用以前,除非该序列是用构造函数(4)构造的,否则必须使用修饰函数length()(13)以设置序列的长度。

对于 string 和对象引用,序列的operator操作返回与结构和数组中的string 和对象引用成员语义相同的类型,所以,正常的string 和对象引用的赋值将释放老的内存。一个合乎规范的程序永远不应该显式地命名此类型。有转化操作将此类型转化为相应的_ptr 类型。

# 有界序列的映射

本节说明有序序列的映射。例如下列IDL描述:

//IDL
//有界序列
typedef sequence <long,10> bounded;
1
2
3

相应映射的C++的代码是

//C++
class _IDL_SEQUENCE_10_long {
	unsigned long _maximum;
	unsigned long _length;
	unsigned long _release;
	long* _buffer;

        public:
  		_IDL_SEQUENCE_10_long();		        (1)
  		_IDL_SEQUENCE_10_long(const _IDL_SEQUENCE_10_long &IDL_s);(2)
  		_IDL_SEQUENCE_10_long(unsigned long length, long* data, 
          unsigned char release = 0);	                        (3)
		~_IDL_SEQUENCE_10_long();			(4)
		_IDL_SEQUENCE_10_long& operator= (const _IDL_SEQUENCE_10_long &IDL_s);(5)
		long& operator [](unsigned long IDL_i){
    	            if(IDL_i >= _length) throw RETURNCODE_NO_MEMORY;
    		    return _buffer[IDL_i];
  		}	                    (6)
		long& operator [](unsigned long IDL_i) const{
    	            if(IDL_i >= _length) throw RETURNCODE_NO_MEMORY;
   		    return _buffer[IDL_i];
  		}	                    (7)
		static long* allocbuf(unsigned long nelems){
    		    return (new long[nelems]);
  		}	                    (8)
		static void freebuf(long* data){
    		    if(data) delete [] data;
  		}	                    (9)
		unsigned long maximum() const {return _maximum; }       (10)
		unsigned long length() const {return _length; }		(11)
		void length(unsigned long len);				(12)
		//其他函数};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

此映射与无界序列类似,不同点将在后面的段落中说明。

最大长度是类型的一部分,不能设置或修改。所以构造函数(1)并未将它设置为0。也为提供一个构造函数来设置最大长度。

函数maximum()(10)总是返回IDL 类型中的序列界限。

序列的内存管理问题

由于可能产生内存管理错误,一般建议不要通过operator来给序列的元素赋值,除非release=1(true)。

当序列由release=1 创建时,一个合乎规范的应用程序不应该用该缓冲区传给构造函数,因为uDDS在拷贝缓冲区后立即删除原来的指针。

# 数组类型的映射

一个IDL的数组映射为:

  • 一个相应的C++数组定义。

  • 一个数组的_var类型。

在IDL和C++里,所有的数组下标都是从0 至<size -1>。如果数组的元素是字符串或对象引用,那到C++的映射与结构成员的映射相同,就是说:对数组成员的赋值将释放原来值的存储空间。

一个数组切片(array slice)是一个具有除原始数组的第一维外所有维的数组。例如,一个二维数组切片是一个一维数组,一个一维数组的切片是一个数组元素的类型。C++的映射对每个数组切片提供了一个类型定义(typedef)。例如:

//IDL
typedef long arrayLong[10];
typedef float arrayFloat[5][3];
1
2
3

映射产生如下的数组和数组切片:

//C++
typedef long arrayLong[10];
typedef long arrayLong_slice;
typedef float arrayFloat[5][3];
typedef float arrayFloat_slice[3];
1
2
3
4
5