OpenDDS简介
Don Busch,首席软件工程师兼合作伙伴
Object Computing,Inc.(OCI)
介绍
分布式实时应用程序有时以数据为中心而不是以服务为中心,这意味着分布式系统中参与者的主要目标是分发应用程序数据,而不是访问共享服务。应用程序数据的提供者和/或使用者的集合在设计时可能是未知的,并且可能会在应用程序的整个生命周期内发生变化。通常,以发布/订阅通信模型而不是请求/响应模型最有效地实现以数据为中心的范例。
用于实时系统的OMG 数据分发服务(DDS)解决了以数据为中心的分布式应用程序的性能要求和实时性要求。DDS增加了分布式实时系统开发人员可以使用的发布/订阅选项的范围。为了方便起见,使用OMG接口定义语言(IDL)定义DDS接口。但是,大多数细节留给实现,最重要的是如何在发布者和订阅者之间进行数据传输。DDS实现者决定将底层数据通过TCP,UDP,UDP多播,共享内存等从发布者移动到订阅者的基础通信机制。使用CORBA或IIOP协议不需要DDS规范的实现。将数据从发布者传输到订阅者。
OpenDDS是OMG数据分发服务规范的开源C ++实现。OpenDDS包含基于文件的配置机制。通过配置文件,OpenDDS用户可以配置发布者或订阅者的传输,调试输出,内存分配,DCPSInfoRepo代理进程的位置以及许多其他设置。《OpenDDS开发人员指南》的“配置”一章中介绍了完整的配置设置集。
在本文中,我们涵盖以下主题:
OMG DDS的OpenDDS实现
DDS架构
股票报价员示例
IDL类型
发行人
订户
订户的听众
建立发布者和订阅者
配置股票报价器
通过TCP传输运行股票报价器
通过UDP传输运行股票报价器
摘要
参考资料
OMG DDS的OpenDDS实现
OpenDDS利用可插拔的传输体系结构,通过应用程序开发人员选择的传输和封送实施实现数据传输。从概念上讲,该体系结构借鉴了TAO的可插入协议框架。OpenDDS当前支持TCP和UDP点对点传输以及不可靠和可靠的多播,并使用高性能的封送处理实现。
这种可插拔的传输体系结构允许DDS用户基于所需的传输以及应用程序部署的同质或异质性质来优化DDS安装。可以做出这些选择而不会影响应用程序代码本身。
封送处理代码由专用的OpenDDS IDL编译器生成。单个单独的DCPS信息存储库(DCPSInfoRepo)流程充当中央票据交换所,将发布者和订阅者联系在一起。在幕后,OpenDDS使用CORBA与DCPSInfoRepo流程进行通信, 以关联发布者和订阅者。发布者和订阅者之间的数据传输直接在发布和订阅过程之间进行。OpenDDS为RB和在发送或接收DDS数据时发生的非CORBA I / O创建自己的线程。
DDS架构
OMG数据分发服务规范将DDS分为两个单独的体系结构层。较低的层是以数据为中心的发布和订阅(DCPS)层,其中包含到发布/订阅通信机制的类型安全接口。上层是数据本地重构层(DLRL),它使应用程序开发人员可以在DCPS层之上构建本地对象模型,从而使应用程序免受DCPS知识的影响。每一层都有自己的概念和使用模式集,因此可以分别讨论这两层的概念和术语。
以数据为中心的发布和订阅-DCPS
DCPS层负责有效地将数据从发布者分发到感兴趣的订阅者。它 在发送方使用发布者和数据写入器 ,在接收方使用订户和数据读取器的概念来实现。DCPS层由一个或多个数据域组成,每个数据域都包含一组通过DDS进行通信的参与者(发布者和订阅者)。每个实体(即发布者或订阅者)都属于一个域。每个进程在其所属的每个数据域中都有一个域参与者。
在任何数据域中,数据都是由主题标识的,该主题是特定于类型的域段,允许发布者和订阅者明确地引用数据。在域中,主题将唯一的主题名称,数据类型和一组服务质量(QoS)策略与数据本身相关联。每个主题仅与一种数据类型相关联,尽管许多不同的主题可以发布相同的数据类型。发布者的行为由与特定数据源的发布者,数据写入者和主题元素相关联的QoS策略确定。同样,订户的行为由与订户,数据读取器和特定数据接收器的主题元素相关联的QoS策略确定。
有关DCPS术语的更多信息,请参见《OpenDDS开发人员指南》。
DDS规范定义了许多服务质量(QoS)策略,应用程序可使用这些策略来指定其可靠性,资源使用,容错以及对服务的其他要求。参与者指定他们从服务中需要的行为;服务决定如何实现这些行为。这些策略可以应用于各种DCPS实体(主题,数据写入器,数据读取器,发布者,订阅者和域参与者),尽管并非所有策略对所有类型的实体都有效。
订阅者和发布者通过报价请求范例协作指定QoS策略。发布者向所有订阅者提供一套QoS策略。订户请求其所需的QoS策略集。然后,DDS实现会尝试将请求的策略与提供的策略进行匹配。如果策略一致,则发布和订阅将匹配。
OpenDDS支持全套DCPS服务质量(QoS)策略,包括:
QoS政策 | 描述 |
---|---|
活泼 | 控制活动性检查,以确保系统中预期的实体仍处于活动状态 |
可靠性 | 确定是否允许该服务删除样本 |
历史 | 控制实例的值发生变化,然后将其传达给所有订阅服务器,该实例发生了什么情况 |
资源限制 | 控制服务可用于满足其他QoS要求的资源 |
有关服务质量策略的更完整列表和更详细的服务质量定义,请参阅对象管理组的DDS白皮书简介附录A。
数据本地重建层-DLRL
数据本地重建层(DLRL)是DCPS之上的面向对象层。DLRL对象是具有一个或多个共享属性的本机语言(即C ++)对象。每个DLRL类都映射到一个或多个DCPS主题。每个共享属性值都映射到主题数据类型的字段,并且其值通过DCPS分布在整个应用程序中。DLRL参与者通过修改DLRL对象将数据与应用程序的其余部分进行通信,从而发布有关主题的数据样本。DLRL共享属性可以是简单的值或结构,对另一个DLRL对象的引用或这些对象的集合(列表,映射)。DLRL支持复杂的对象图和DLRL对象之间的复杂关系。
开发人员负责确定DCPS实体如何映射到DLRL对象。使用IDL值类型在OMG接口定义语言(IDL)中指定模型。映射在概念上类似于对象关系数据库映射,后者将对象模型映射到关系数据库表。我们认为每个DCPS主题都类似于关系数据库表,每个样本都作为该表中的一行。DDS规范具有从DCPS到DLRL的默认映射。或者,开发人员可以选择通过XML映射文件指定自己的自定义映射。
OpenDDS当前未实现DLRL。
OpenDDS股票报价器示例
我们的示例说明了通过DDS DCPS层发布和订阅数据样本。该示例包含两个DCPS主题,都与股市有关。
股票报价发布者将股票报价样本发布给感兴趣的订阅者;每个报价均包含证券的股票代码,其价值和时间戳。报价在整个交易日中定期发布,因为买卖交易会影响证券的基础价值。另外,证券交易所事件发布者发布与证券交易所有关的重要事件,即,何时交易所开放,关闭,何时暂停交易或恢复交易。
我们的订户同时订阅股票报价和股票交易所事件。订户打印其所看到的每个报价的代码符号和值。当订阅者收到表明当天股票交易所已经关闭的事件时,它将正常关闭。因此,“封闭”证券交易所事件的接收是订户停止预期股票报价样本的信号。
我们将演示如何使用相同的发布者和订阅者代码通过TCP和UDP传输进行通信。传输配置隔离在一组配置文件中,使我们无需更改任何代码即可切换传输。
IDL类型
首先,我们在IDL中定义已发布的DDS数据类型:
#include "orbsvcs/TimeBase.idl"
module StockQuoter
{
#pragma DCPS_DATA_TYPE "StockQuoter::Quote"
#pragma DCPS_DATA_KEY "StockQuoter::Quote ticker"
struct Quote {
string ticker;
string exchange;
string full_name;
double value;
TimeBase::TimeT timestamp;
};
#pragma DCPS_DATA_TYPE "StockQuoter::ExchangeEvent"
#pragma DCPS_DATA_KEY "StockQuoter::ExchangeEvent exchange"
enum ExchangeEventType {
TRADING_OPENED,
TRADING_CLOSED,
TRADING_SUSPENDED,
TRADING_RESUMED };
struct ExchangeEvent {
string exchange;
ExchangeEventType event;
TimeBase::TimeT timestamp;
};
};
我们发布两种数据类型:每个股票报价的报价类型,以及用于指示何时打开,关闭证券交易所以及何时暂停或恢复交易的ExchangeEvent类型。该DCPS_DATA_TYPE
编译标记的类型与DDS使用。的 DCPS_DATA_KEY
每种类型的定义是针对每个唯一标识符 的实例中的数据类型的。我们报价类型的关键是股票的股票代码。一整天,我们希望为每个股票代号发布许多值或样本。每个股票代号的已发布样本集属于同一实例。在我们的示例中,我们将发布两个股票代号,并因此发布两个实例:SPY(标准普尔存托凭证,即S&P 500)和MDY(S&P中盘存托凭证,即S&P中盘400)。
接下来,我们使用OpenDDS的opendds_idl
编译器编译IDL 以生成 类型支持代码。类型支持代码包括生成的DCPS 数据写入器和数据读取器 C ++类以及其他IDL代码。DDS使用类型安全的接口进行发布和订阅。类型安全的接口具有几个优点:首先,在编译时更容易捕获编程错误;第二,当在编译时已知封送数据类型时,可以使生成的封送代码非常高效。第三,我们可以避免any在数据传输中使用低效类型,例如CORBA 。
生成股票报价器的IDL类型的类型支持代码的命令如下:
$ DDS_ROOT / bin / opendds_idl StockQuoter.idl
此命令生成以下文件:
StockQuoterTypeSupport.idl
StockQuoterTypeSupportImpl.h
StockQuoterTypeSupportImpl.cpp
但是,我们不需要opendds_idl
手动运行编译器。稍后,我们将使用Make Project Creator(MPC)项目为我们自动化构建步骤。
接下来,我们使用TAO的IDL编译器来编译所有三个IDL文件- StockQuoter.idl
我们手动编写的 文件,以及由生成的类型支持文件opendds_idl
。
tao_idl -I $ DDS_ROOT -I $ TAO_ROOT / orbsvcs StockQuoter.idl
tao_idl -I $ DDS_ROOT -I $ TAO_ROOT / orbsvcs StockQuoterTypeSupport.idl
目录
发行人
接下来,我们编写一个发布者,以通过DDS发布股票报价和股票交易所事件。首先,我们包括由opendds_idl编译器生成的两个类型支持头文件。
#include "StockQuoterTypeSupportImpl.h"
我们还包括DCPS发布者,服务参与者和QoS标头文件。
#include "dds/DCPS/Service_Participant.h"
#include "dds/DCPS/Marked_Default_Qos.h"
#include "dds/DCPS/PublisherImpl.h"
#include "ace/streams.h"
#include "orbsvcs/Time_Utilities.h"
以下常量用于我们的域,类型名称和主题名称。每种类型均在单独的主题上发布。订户必须为其域,类型名称和主题名称使用相同的值。
// constants for Stock Quoter domain Id, types, and topic
DDS::DomainId_t QUOTER_DOMAIN_ID = 1066;
const char* QUOTER_QUOTE_TYPE = "Quote Type";
const char* QUOTER_QUOTE_TOPIC = "Stock Quotes";
const char* QUOTER_EXCHANGE_EVENT_TYPE = "Exchange Event Type";
const char* QUOTER_EXCHANGE_EVENT_TOPIC = "Stock Exchange Events";
在发布证券交易所事件(即打开,关闭,暂停或恢复)时,我们还将发布该事件适用的证券交易所的名称。
const char* STOCK_EXCHANGE_NAME = "Test Stock Exchange";
这是获取当前日期和时间的简单辅助方法。
TimeBase::TimeT get_timestamp()
{
TimeBase::TimeT retval;
ACE_hrtime_t t = ACE_OS::gethrtime ();
ORBSVCS_Time::hrtime_to_TimeT (retval, t);
return retval;
}
发布者的源代码文件的其余部分包含main()。我们输入发布者的main()
int main (int argc, char *argv[])
{
DDS::DomainParticipantFactory_var dpf =
DDS::DomainParticipantFactory::_nil();
DDS::DomainParticipant_var participant =
DDS::DomainParticipant::_nil();
try
{
首先,我们创建一个域参与者。DDS发布者可以在多个独立域上发布,但是我们的示例仅在一个域上发布。我们使用TheDomainParticipantFactoryWithArgs
宏将命令行参数传递到DCPS中,并获得单例域参与者工厂。我们使用域参与者的默认服务质量策略为“ Quote”域创建一个域参与者。QUOTER_DOMAIN_ID
传递给工厂的值在发布者和订阅者中必须相同。
// Initialize, and create a DomainParticipant
dpf = TheParticipantFactoryWithArgs(argc, argv);
participant = dpf->create_participant(
QUOTER_DOMAIN_ID,
PARTICIPANT_QOS_DEFAULT,
DDS::DomainParticipantListener::_nil());
if (CORBA::is_nil (participant.in ()))
{
cerr << “create_participant failed.” << endl;
ACE_OS::exit(1);
}
然后,我们通过域参与者使用默认的服务质量值创建发布者。PublisherListener
当某些与发布相关的事件发生时,我们可以附加一个DCPS调用的。但是,我们不在乎那些事件,因此我们附加了一个nil侦听器。
// Create a publisher for the two topics
// (PUBLISHER_QOS_DEFAULT is defined in
// Marked_Default_Qos.h)
DDS::Publisher_var pub =
participant->create_publisher(
PUBLISHER_QOS_DEFAULT,
DDS::PublisherListener::_nil());
if (CORBA::is_nil (pub.in ()))
{
cerr << "create_publisher failed." << endl;
ACE_OS::exit(1);
}
通过DCPS进行发布涉及三个步骤。首先,我们为发布的数据样本注册每种类型。我们的示例发布了两种IDL类型的示例Quote和ExchangeEvent。其次,我们创建一个或多个发布主题。每个主题只能绑定一种类型。因此,我们为两种类型的每种类型创建一个主题。第三,我们为每个主题创建一个数据编写器,并通过该数据编写器发布示例。
我们首先向域参与者注册IDL Quote类型,并为Quote类型传递生成的QuoteTypeSupportImpl
类的实例。我们用于报价类型的名称(存储在常量值中QUOTER_QUOTE_TYPE
)必须与订阅服务器上使用的名称匹配。创建主题时,我们指定此类型名称,从而使DCPS能够在以后为该主题创建适当类型的数据写入器。
// Register the Quote type
StockQuoter::QuoteTypeSupport_var quote_servant
= new StockQuoter::QuoteTypeSupportImpl();
if (DDS::RETCODE_OK !=
quote_servant->register_type(participant.in (),
QUOTER_QUOTE_TYPE))
{
cerr << "register_type for " << QUOTER_QUOTE_TYPE
<< " failed." << endl;
ACE_OS::exit(1);
}
然后,我们使用生成的ExchangeEventTypeSupportImpl
类以相同的方式向域参与者注册IDL ExchangeEvent类型。我们的DCPS域参与者可以发布有关Quote或ExchangeEvent类型的主题。
// Register the ExchangeEvent type
StockQuoter::ExchangeEventTypeSupport_var exchange_evt_servant
= new StockQuoter::ExchangeEventTypeSupportImpl();
if (DDS::RETCODE_OK !=
exchange_evt_servant->register_type(
participant.in ()