OpenDDS

OpenDDS是OMG数据分发服务(DDS)规范的开源C++实现,专注于以数据为中心的分布式应用。DDS提供高性能的发布/订阅通信模型,而OpenDDS具有可插拔的传输架构,支持TCP、UDP等多种传输方式。文章通过股票报价器示例展示了OpenDDS的使用,包括IDL类型、发布者、订阅者、传输配置等。OpenDDS的配置文件允许用户灵活配置发布者和订阅者的行为,而无需修改代码即可切换传输方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 (),
            QUOTER_EXCHANGE_EVENT_TYPE))
    {
      cerr << "register_type for "
           << QUOTER_EXCHANGE_EVENT_TYPE
           << " failed." << endl;
      ACE_OS::exit(1);
    }

我们为报价样本创建一个主题,指示报价数据类型的主题名称和注册名称,并使用默认的服务质量设置。同样,引用主题和类型名称必须在发布者和订阅者上匹配。

// Get QoS to use for our two topics
// Could also use TOPIC_QOS_DEFAULT instead
DDS::TopicQos default_topic_qos;
participant->get_default_topic_qos(default_topic_qos);

// Create a topic for the Quote type…
DDS::Topic_var quote_topic =
participant->create_topic (QUOTER_QUOTE_TOPIC,
QUOTER_QUOTE_TYPE,
default_topic_qos,
DDS::TopicListener::_nil());

if (CORBA::is_nil (quote_topic.in ()))
{
cerr << “create_topic for "
<< QUOTER_QUOTE_TOPIC
<< " failed.” << endl;
ACE_OS::exit(1);
}

同样,我们为ExchangeEvent示例创建一个主题,指示主题名称和ExchangeEvent类型的注册名称,并使用默认的服务质量设置。同样,证券交易所事件主题和类型名称必须在发布者和订阅者上匹配。

  // .. and another topic for the Exchange Event type
    DDS::Topic_var exchange_evt_topic =
      participant->create_topic (QUOTER_EXCHANGE_EVENT_TOPIC,
                                 QUOTER_EXCHANGE_EVENT_TYPE,
                                 default_topic_qos,
                                 DDS::TopicListener::_nil());

    if (CORBA::is_nil (exchange_evt_topic.in ()))
    {
      cerr << "create_topic for "
           << QUOTER_EXCHANGE_EVENT_TOPIC
           << " failed."
           << endl;
      ACE_OS::exit(1);
    }

我们创建了两个数据编写器,每个主题一个。我们传入上面创建的主题;该主题知道其类型。每个数据编写者都与一个发布者正好相关联,并就一个主题进行发布。后来,我们的发布者通过将数据样本写入每个数据写入器来发布每个主题。以下代码为“股票报价”主题创建数据编写器。

  // Get QoS to use for our two DataWriters
    // Could also use DATAWRITER_QOS_DEFAULT
    DDS::DataWriterQos dw_default_qos;
    pub->get_default_datawriter_qos (dw_default_qos);

    // Create a DataWriter for the Quote topic
    DDS::DataWriter_var quote_base_dw =
      pub->create_datawriter(quote_topic.in (),
                             dw_default_qos,
                             DDS::DataWriterListener::_nil());

    if (CORBA::is_nil (quote_base_dw.in ()))
    {
      cerr << "create_datawriter for "
           << QUOTER_QUOTE_TOPIC
           << " failed." << endl;
      ACE_OS::exit(1);
    }

    StockQuoter::QuoteDataWriter_var quote_dw
      = StockQuoter::QuoteDataWriter::_narrow(quote_base_dw.in());
    if (CORBA::is_nil (quote_dw.in ()))
    {
      cerr << "QuoteDataWriter could not be narrowed"
           << endl;
      ACE_OS::exit(1);
    }

然后,我们为“证券交易所事件”主题创建一个数据编写器。同样,我们传入上面创建的主题,该主题知道其类型。

 // Create a DataWriter for the Exchange Event topic
    DDS::DataWriter_var exchange_evt_base_dw =
      pub->create_datawriter(exchange_evt_topic.in (),
                             dw_default_qos,
                             DDS::DataWriterListener::_nil());

    if (CORBA::is_nil (exchange_evt_base_dw.in ()))
    {
      cerr << "create_datawriter for "
           << QUOTER_EXCHANGE_EVENT_TOPIC
           << " failed." << endl;
      ACE_OS::exit(1);
    }

    StockQuoter::ExchangeEventDataWriter_var exchange_evt_dw =
      StockQuoter::ExchangeEventDataWriter::_narrow(
        exchange_evt_base_dw.in());

    if (CORBA::is_nil (exchange_evt_dw.in ()))
    {
      cerr << "ExchangeEventDataWriter could not "
           << "be narrowed"<< endl;
      ACE_OS::exit(1);
    }

我们可以选择注册每个数据实例。注册每个数据实例将在编写该实例的样本时稍微改善延迟。

发布者可以在每个数据实例上发布许多数据样本。数据实例由唯一键标识。对于Quote类型,我们将ticker 其标识为IDL类型定义中的关键字段。具有相同键值的每个Quote数据样本均被视为同一数据实例的一部分。换句话说,在股票代码“ SPY”上发布的每个Quote示例都是同一实例的一部分。

我们有两个Quote实例,分别是股票代码“ SPY”(标准普尔存托凭证,即S&P 500)和“ MDY”(标准普尔中型存托凭证,即S&P Midcap 400),以及一个ExchangeEvent实例,用于“测试证券交易所”。 ”。我们向适当的数据写入器注册每个实例。实际调用了注册方法,_cxx_register因为它register是C ++中的保留字。

 // Register the Exchange Event and the two
    // Quoted securities (SPY and MDY) with the
    // appropriate data writer

    StockQuoter::Quote spy;
    spy.ticker = CORBA::string_dup("SPY");
    DDS::InstanceHandle_t spy_handle =
      quote_dw->_cxx_register(spy);

    StockQuoter::Quote mdy;
    mdy.ticker = CORBA::string_dup("MDY");
    DDS::InstanceHandle_t mdy_handle =
      quote_dw->_cxx_register(mdy);

    StockQuoter::ExchangeEvent ex_evt;
    ex_evt.exchange = STOCK_EXCHANGE_NAME;
    DDS::InstanceHandle_t exchange_handle =
      exchange_evt_dw->_cxx_register(ex_evt);

最后,我们发布。首先,我们发布TRADING_OPENED有关“证券交易所事件”主题的事件。

  // Publish...

    StockQuoter::ExchangeEvent opened;
    opened.exchange = STOCK_EXCHANGE_NAME;
    opened.event = StockQuoter::TRADING_OPENED;
    opened.timestamp = get_timestamp();

    cout << "Publishing TRADING_OPENED" << endl;
    DDS::ReturnCode_t ret =
      exchange_evt_dw->write(opened, exchange_handle);

    if (ret != DDS::RETCODE_OK)
    {
      ACE_ERROR ((
        LM_ERROR,
        ACE_TEXT("(%P|%t)ERROR: OPEN write returned %d.\n"),
        ret));
    }

然后,我们在“股票报价”主题上发布“ SPY”和“ MDY”实例的几个股票报价数据样本。我们简单地循环,每次增加一点报价符号的报价,以模拟在真正美好的一天中的活跃交易。

ACE_Time_Value quarterSecond( 0, 250000 );

for ( int i = 0; i < 20; ++i )
{
//
// SPY
//

StockQuoter::Quote spy_quote;
spy_quote.exchange = STOCK_EXCHANGE_NAME;
spy_quote.ticker = CORBA::string_dup(“SPY”);
spy_quote.full_name =
CORBA::string_dup(“S&P Depository Receipts”);
spy_quote.value = 1200.0 + 10.0*i;
spy_quote.timestamp = get_timestamp();

cout << "Publishing SPY Quote: "
<< spy_quote.value << endl;

ret = quote_dw->write(spy_quote, spy_handle);

if (ret != DDS::RETCODE_OK)
{
ACE_ERROR ((
LM_ERROR,
ACE_TEXT("(%P|%t)ERROR: SPY write returned %d.\n"),
ret));
}

ACE_OS::sleep( quarterSecond );

//
// MDY
//

StockQuoter::Quote mdy_quote;
mdy_quote.exchange = STOCK_EXCHANGE_NAME;
mdy_quote.ticker = CORBA::string_dup(“MDY”);
mdy_quote.full_name =
CORBA::string_dup(“S&P Midcap Depository Receipts”);
mdy_quote.value = 1400.0 + 10.0*i;
mdy_quote.timestamp = get_timestamp();

cout << "Publishing MDY Quote: "
<< mdy_quote.value << endl;

ret = quote_dw->write(mdy_quote, mdy_handle);

if (ret != DDS::RETCODE_OK)
{
ACE_ERROR ((
LM_ERROR,
ACE_TEXT("(%P|%t)ERROR: MDY write returned %d.\n"),
ret));
}

ACE_OS::sleep( quarterSecond );
}

最后,我们TRADING_CLOSED在“证券交易所事件”主题上发布事件,以指示该证券交易所当天关闭。


```cpp
 StockQuoter::ExchangeEvent closed;
    closed.exchange = STOCK_EXCHANGE_NAME;
    closed.event = StockQuoter::TRADING_CLOSED;
    closed.timestamp = get_timestamp();

    cout << "Publishing TRADING_CLOSED" << endl;

    ret = exchange_evt_dw->write(closed, exchange_handle);

    if (ret != DDS::RETCODE_OK)
    {
      ACE_ERROR ((
        LM_ERROR,
        ACE_TEXT("(%P|%t)ERROR: CLOSED write returned %d.\n"),
        ret));
    }

    cout << "Exiting..." << endl;
  } catch (CORBA::Exception& e) {
    cerr << "Exception caught in main.cpp:" << endl
         << e << endl;
    ACE_OS::exit(1);
  }
最后,我们在离开之前先清理自己。

  // Cleanup
  try {
    if (!CORBA::is_nil (participant.in ())) {
      participant->delete_contained_entities();
    }
    if (!CORBA::is_nil (dpf.in ())) {
      dpf->delete_participant(participant.in ());
    }
  } catch (CORBA::Exception& e) {
    cerr << "Exception caught in cleanup."
         << endl
         << e << endl;
    ACE_OS::exit(1);
  }
  TheServiceParticipant->shutdown ();
  return 0;
}

这就完成了发布者的C ++代码。

目录

## 订户

我们的订户订阅股票报价和证券交易所事件,从发布者那里接收数据样本。我们使用发布者的`TRADING_CLOSED`事件来表示当天的交易已经结束,从而触发了订阅者的正常关闭。

订户中的许多代码与发布者中的代码相似。我们以与发布者中相同的方式获得域参与者,注册类型等。主要区别在于订阅者是被动的,等待接收样本,而发布者是主动的。订阅服务器使用侦听器对象从发布服务器接收样本。

首先,我们包括由dcps_ts.pl 脚本生成的两个类型支持头文件。我们还将这些文件包含在发布者中。但是,我们还包括两个侦听器头文件,每种发布类型一个。当在相关主题上发布数据样本时,DDS会调用侦听器。

```cpp
#include "StockQuoterTypeSupportImpl.h"
#include "ExchangeEventDataReaderListenerImpl.h"

我们还包括DCPS订户,服务参与者和QoS标头文件。

#include "dds/DCPS/Service_Participant.h"
#include "dds/DCPS/Marked_Default_Qos.h"
#include "dds/DCPS/SubscriberImpl.h"
#include "dds/DCPS/BuiltinTopicUtils.h"
#include "ace/streams.h"
#include "orbsvcs/Time_Utilities.h"

以下常量用于我们的域,类型名称和主题名称。这些名称必须与发布者使用的域,类型名称和主题名称匹配。

// constants for Stock Quoter domain Id, types, and topic
// (same as publisher)
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";

订户的源代码文件的其余部分包含main()。我们输入订户的main()。

int main (int argc, char *argv[])
{
  DDS::DomainParticipantFactory_var dpf =
    DDS::DomainParticipantFactory::_nil();

  DDS::DomainParticipant_var participant =
    DDS::DomainParticipant::_nil();

  try {

就像在发布者中一样,我们创建域参与者。指定的域与发布者的域匹配。

 // Initialize, and create a DomainParticipant
    // (same code as publisher)

    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);
    }

然后,我们通过域参与者使用默认的服务质量策略值创建一个订户。SubscriberListener 当某些与订阅相关的事件发生时,我们可以附加一个DCPS调用的。但是,我们不在乎那些事件,因此我们将其设置为零。这几乎与我们致电时在发布商中所做的相同create_publisher。

// Create a subscriber for the two topics
// (SUBSCRIBER_QOS_DEFAULT is defined
// in Marked_Default_Qos.h)
DDS::Subscriber_var sub =
participant->create_subscriber(
SUBSCRIBER_QOS_DEFAULT,
DDS::SubscriberListener::_nil());

if (CORBA::is_nil (sub.in ()))
{
cerr << “create_subscriber failed.” << endl;
ACE_OS::exit(1);
}

与发布者一样,我们必须向域参与者注册IDL Quote和ExchangeEvent类型,才能订阅有关这些类型的主题。

// Register the Quote type
// (same code as publisher)
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);
}

// Register the ExchangeEvent type
// (same code as publisher)
StockQuoter::ExchangeEventTypeSupport_var exchange_evt_servant
= new StockQuoter::ExchangeEventTypeSupportImpl();

if (DDS::RETCODE_OK !=
exchange_evt_servant->register_type(
participant.in (),
QUOTER_EXCHANGE_EVENT_TYPE))
{
cerr << “register_type for "
<< QUOTER_EXCHANGE_EVENT_TYPE
<< " failed.” << endl;
ACE_OS::exit(1);
}

与发布者一样,我们为股票报价创建一个主题,指示主题名称和报价类型的注册名称,并使用默认的服务质量设置。同样,股票报价主题名称必须在发布者和订阅者上匹配。

// Get QoS to use for our two topics
// Could also use TOPIC_QOS_DEFAULT instead
// (same code as publisher)
DDS::TopicQos default_topic_qos;
participant->get_default_topic_qos(default_topic_qos);

// Create a topic for the Quote type…
// (same code as publisher)
DDS::Topic_var quote_topic =
participant->create_topic (QUOTER_QUOTE_TOPIC,
QUOTER_QUOTE_TYPE,
default_topic_qos,
DDS::TopicListener::_nil());

if (CORBA::is_nil (quote_topic.in ()))
{
cerr << “create_topic for "
<< QUOTER_QUOTE_TOPIC
<< " failed.” << endl;
ACE_OS::exit(1);
}

同样,我们为ExchangeEvent示例创建一个主题,指示主题名称和ExchangeEvent类型的注册名称,并使用默认的服务质量设置。同样,证券交易所事件主题名称必须在发布者和订阅者上匹配。

  // .. and another topic for the Exchange Event type
    // (same code as publisher)
    DDS::Topic_var exchange_evt_topic =
      participant->create_topic (QUOTER_EXCHANGE_EVENT_TOPIC,
                                 QUOTER_EXCHANGE_EVENT_TYPE,
                                 default_topic_qos,
                                 DDS::TopicListener::_nil());

    if (CORBA::is_nil (exchange_evt_topic.in ()))
    {
      cerr << "create_topic for "
           << QUOTER_EXCHANGE_EVENT_TOPIC
           << " failed."
           << endl;
      ACE_OS::exit(1);
    }

在发布者上,我们创建了两个数据编写器,每个主题一个。在订户上,我们将创建两个数据读取器,每个主题一个。每个数据读取器只有一个订户,并订阅一个主题。我们还在每个数据读取器上附加了一个侦听器,以接收发布的数据样本的通知。这是发布者和订阅者代码不同的地方。

以下代码为“股票报价”主题创建一个侦听器。侦听器是一个本地CORBA对象,实现了DDS::DataReaderListenerIDL接口。我们使用OpenDDS的便捷servant_to_reference功能模板来获取接口类型的引用。

 // Create DataReaders and DataReaderListeners for the
    // Quote and ExchangeEvent

    // Create a Quote listener
    QuoteDataReaderListenerImpl quote_listener_servant;

    DDS::DataReaderListener_var quote_listener =
      ::OpenDDS::DCPS::servant_to_reference(&quote_listener_servant);

    if (CORBA::is_nil (quote_listener.in ()))
    {
      cerr << "Quote listener is nil." << endl;
      ACE_OS::exit(1);
    }

我们为“证券交易所事件”主题创建第二个侦听器。

// Create an ExchangeEvent listener
ExchangeEventDataReaderListenerImpl exchange_evt_listener_servant;

DDS::DataReaderListener_var exchange_evt_listener =
  ::OpenDDS::DCPS::servant_to_reference(&exchange_evt_listener_servant);

if (CORBA::is_nil (exchange_evt_listener.in ()))
{
  cerr << "ExchangeEvent listener is nil." << endl;
  ACE_OS::exit(1);
}

最后,我们为两个主题中的每个主题创建一个数据读取器。首先,我们为“股票报价”主题创建一个数据读取器,并附加上面创建的相关侦听器。

 // Create the Quote DataReader

    // Get the default QoS
    // Could also use DATAREADER_QOS_DEFAULT
    DDS::DataReaderQos dr_default_qos;
    sub->get_default_datareader_qos (dr_default_qos);

    DDS::DataReader_var quote_dr =
      sub->create_datareader(quote_topic.in (),
                             dr_default_qos,
                             quote_listener.in ());

然后,我们为“证券交易所事件”主题创建一个数据读取器,并附加上面创建的另一个侦听器。

// Create the ExchangeEvent DataReader

DDS::DataReader_var exchange_evt_dr =
sub->create_datareader(exchange_evt_topic.in (),
dr_default_qos,
exchange_evt_listener.in ());

OpenDDS产生它自己的线程来处理来自发布者的传入事件。因此,订户中没有事件循环代码。但是,在准备关闭整个订户进程之前,我们必须确保不允许主线程退出。因此,我们循环处理股票报价和证券交易所事件,直到TRADING_CLOSED 在“证券交易所事件”主题上收到该事件为止。本质上,我们希望接收已发布的数据样本,直到证券交易所告诉我们它已经关闭。该sleep调用使我们每秒检查一次,以避免消耗过多的CPU。

// Wait for events from the Publisher; shut
// down when “close” received
cout << “Subscriber: waiting for events” << endl;
while ( ! exchange_evt_listener_servant.
is_exchange_closed_received() )
{
ACE_OS::sleep(1);
}

收到TRADING_CLOSED事件后,我们可以正常退出循环。

 cout << "Received CLOSED event from publisher; "
         << " exiting..."
         << endl;

  } catch (CORBA::Exception& e) {
    cerr << "Exception caught in main.cpp:" << endl
         << e << endl;
    ACE_OS::exit(1);
  }

最后,我们在离开之前先清理自己。

 // Cleanup
  try {
    if (!CORBA::is_nil (participant.in ())) {
      participant->delete_contained_entities();
    }
    if (!CORBA::is_nil (dpf.in ())) {
      dpf->delete_participant(participant.in ());
    }
  } catch (CORBA::Exception& e) {
    cerr << "Exception caught in cleanup."
         << endl
         << e << endl;
    ACE_OS::exit(1);
  }
  TheServiceParticipant->shutdown ();
  return 0;
}

目录

订阅者的“股票报价”和“证券交易所事件”侦听器

“股票行情”数据阅读器和“股票交易所事件”数据阅读器均附带一个侦听器。每当从发布者接收到数据样本时,DDS框架就会调用这些侦听器。我们已决定两个数据读取器中的每一个都应具有自己的侦听器,尽管如果我们对侦听器进行编码以处理两种数据类型,则可以为两个数据读取器使用单个侦听器。

每个侦听器都实现DDS::DataReaderListenerIDL接口。我们在上面的订户代码中同时使用了a QuoteDataReaderListenerImplan ExchangeEventDataReaderListenerImpl,但尚未定义这些类。我们现在将这样做。

首先,我们为Quote类型的数据读取器编写一个侦听器。该侦听器类实现DDS::DataReaderListener IDL接口,该接口重写了七个纯虚方法。它是IDL接口的CORBA本地对象实现,继承自IDL接口的生成类。

侦听器类必须重写所有七个方法,包括侦听器的实现为空的方法。但是,为简单起见,我们将仅显示该on_data_available 方法,当有新的Quote数据样本可用时将调用该方法。其他六个方法具有空的实现。我们还将使用默认的构造函数和析构函数。

#include "StockQuoterTypeSupportC.h"
#include "StockQuoterTypeSupportImpl.h"
#include "dds/DCPS/Service_Participant.h"
#include "dds/DdsDcpsSubscriptionS.h"
#include "ace/streams.h"

class QuoteDataReaderListenerImpl
  : public virtual OpenDDS::DCPS::LocalObject<DDS::DataReaderListener>
{
public:
  // DDS calls on_data_available on the listener for each
  // received Quote sample.
  virtual void on_data_available(DDS::DataReader_ptr reader)
    throw (CORBA::SystemException)
  {
    try
    {

我们首先将数据读取器参数的值缩小为Quote样本的适当类型。

StockQuoter::QuoteDataReader_var quote_dr =
    StockQuoter::QuoteDataReader::_narrow(reader);

  if (CORBA::is_nil (quote_dr.in ()))
  {
    cerr << "QuoteDataReaderListenerImpl:: "
         << "on_data_available:"
         << " _narrow failed." << endl;
    ACE_OS::exit(1);
  }

然后,我们从数据读取器中获取下一个Quote示例。请注意QuoteDataReader接口的类型安全。

  StockQuoter::Quote quote;
  DDS::SampleInfo si;

  DDS::ReturnCode_t status =
    quote_dr->take_next_sample(quote, si) ;

收到报价样本后,我们只需打印其内容即可。

if (status == DDS::RETCODE_OK) {
    cout << "Quote: ticker    = " << quote.ticker.in()
         << endl
         << "       exchange  = " << quote.exchange.in()
         << endl
         << "       full name = " << quote.full_name.in()
         << endl
         << "       value     = " << quote.value
         << endl
         << "       timestamp = " << quote.timestamp
         << endl;

    cout << "SampleInfo.sample_rank = "
         << si.sample_rank << endl;
  }
  else if (status == DDS::RETCODE_NO_DATA)
  {
    cerr << "ERROR: reader received DDS::RETCODE_NO_DATA!"
         << endl;
  }
  else
  {
    cerr << "ERROR: read Quote: Error: "
         <<  status << endl;
  }

当报价样本超出范围时,堆栈将清除报价样本的内存。

    } catch (CORBA::Exception& e) {
      cerr << "Exception caught in read:"
           << endl << e << endl;
      ACE_OS::exit(1);
    }
  }

我们没有在DDS::DataReaderListener接口中显示其他方法的实现,但是即使它们的实现为空,我们也必须重写它们。

 // must also override:
  //    on_requested_deadline_missed
  //    on_requested_incompatible_qos
  //    on_liveliness_changed
  //    on_subscription_match
  //    on_sample_rejected
  //    on_sample_lost
};

接下来,我们为ExchangeEvent类型的数据读取器编写一个侦听器。基本结构与相同QuoteDataReaderListenerImpl。

#include "ExchangeEventDataReaderListenerImpl.h"
#include "StockQuoterTypeSupportImpl.h"
#include "dds/DCPS/Service_Participant.h"
#include "dds/DdsDcpsSubscriptionS.h"
#include "ace/streams.h"
#include "ace/Synch.h"

class ExchangeEventDataReaderListenerImpl
  : public virtual OpenDDS::DCPS::LocalObject<DDS::DataReaderListener>
{
public:

我们将该is_exchange_closed_received方法添加到数据读取器中,以便订户的主程序可以找出何时TRADING_CLOSED 收到证券交易所事件。此方法在互斥锁的保护下检查布尔值。收到股票交易事件on_data_available时,布尔值由侦听器的方法设置TRADING_CLOSED。

// app-specific
  CORBA::Boolean is_exchange_closed_received()
  {
    ACE_Guard<ACE_Mutex> guard(this->lock_);
    return this->is_exchange_closed_received_;
  }

DDS on_data_available在侦听器上为每个接收到的ExchangeEvent示例进行调用。

 virtual void on_data_available(DDS::DataReader_ptr reader)
    throw (CORBA::SystemException)
  {
    try
    {

与中的一样QuoteDataReaderListenerImpl,我们首先将数据读取器参数的值缩小为适当的类型,在这种情况下为ExchangeEventDataReader。

StockQuoter::ExchangeEventDataReader_var exchange_evt_dr =
StockQuoter::ExchangeEventDataReader::_narrow(reader);

if (CORBA::is_nil (exchange_evt_dr.in ())) {
cerr << “ExchangeEventDataReaderListenerImpl:: "
<< “on_data_available:”
<< " _narrow failed.”
<< endl;
ACE_OS::exit(1);
}

然后,我们从数据读取器中获取下一个ExchangeEvent示例。注意类型安全。

StockQuoter::ExchangeEvent exchange_evt;
DDS::SampleInfo si;

DDS::ReturnCode_t status =
exchange_evt_dr->take_next_sample(exchange_evt, si) ;

收到ExchangeEvent示例后,我们只需打印其内容即可。

if (status == DDS::RETCODE_OK) {
cout << "ExchangeEvent: exchange = "
<< exchange_evt.exchange.in() << endl;

switch ( exchange_evt.event ) {
  case StockQuoter::TRADING_OPENED:
    cout << "TRADING_OPENED" << endl;
    break;

收到TRADING_CLOSED事件后,我们将设置一个标志,指示当天该证券交易所已经关闭。

case StockQuoter::TRADING_CLOSED: {
cout << “TRADING_CLOSED” << endl;

    ACE_Guard<ACE_Mutex> guard(this->lock_);
    this->is_exchange_closed_received_ = 1;
    break;
  }
  case StockQuoter::TRADING_SUSPENDED:
    cout << "TRADING_SUSPENDED" << endl;
    break;
  case StockQuoter::TRADING_RESUMED:
    cout << "TRADING_RESUMED" << endl;
    break;
  default:
    cerr << "ERROR: reader received unknown "
         << "ExchangeEvent: "
         << exchange_evt.event
         << endl;
}

cout << "timestamp = "
     << exchange_evt.timestamp
     << endl;

cout << "SampleInfo.sample_rank = "
     << si.sample_rank
     << endl;

}
else if (status == DDS::RETCODE_NO_DATA)
{
cerr << "ERROR: reader received "
<< “DDS::RETCODE_NO_DATA!”
<< endl;
}
else
{
cerr << "ERROR: read ExchangeEvent: Error: "
<< status
<< endl;
}

超出范围时,堆栈会清除ExchangeEvent示例。

    } catch (CORBA::Exception& e) {
      cerr << "Exception caught in read:" << endl
           << e << endl;
      ACE_OS::exit(1);
    }
  }

  // must also override:
  //    on_requested_deadline_missed
  //    on_requested_incompatible_qos
  //    on_liveliness_changed
  //    on_subscription_match
  //    on_sample_rejected
  //    on_sample_lost

我们添加了两个私有类属性,以跟踪TRADING_CLOSED事件并使用锁保护该值。

private:
  CORBA::Boolean is_exchange_closed_received_;
  ACE_Mutex lock_;
};

这样就完成了订户的C ++代码。

目录

建立发布者和订阅者

我们使用MPC,即Make Project Creator,为发布者和订阅者生成构建文件。MPC提供了一种简单的语法,并且能够为GNU Make,Visual C ++和许多其他构建系统生成构建文件。有关MPC的更多信息,请参见OCI的MPC页面,网址为http://www.objectcomputing.com/products/mpc。

我们创建两个文件来构建我们的股票报价器,一个工作区文件和一个项目文件。我们的工作区文件只是告诉MPC在哪里可以找到MPC dcps和dcpsexe基础项目文件,我们将在以后使用它们。

//
// file StockQuoter.mwc
//

workspace {
  cmdline += -relative DDS_ROOT=$DDS_ROOT
}

接下来,我们创建一个包含三个项目的MPC文件-一个包含IDL和TypeSupport文件的Common项目,一个Publisher,一个Subscriber。这三个项目中的每一个都继承自dcps或的dcpsexe基础项目(位于)$DDS_ROOT。首先,我们创建一个名为StockQuoterCommon的库来保存由TAO IDL和opendds_idl编译器生成的代码。

//
// file StockQuoter.mpc
//

project(*Common) : dcps {
  sharedname = StockQuoterCommon
  libout = .
  includes += $(TAO_ROOT)/orbsvcs
  idlflags += -I$(TAO_ROOT)/orbsvcs
  idlflags += -Wb,export_macro=StockQuoterCommon_Export
  idlflags += -Wb,export_include=StockQuoterCommon_Export.h
  dcps_ts_flags += --export=StockQuoterCommon_Export
  dynamicflags = STOCKQUOTERCOMMON_BUILD_DLL

一个dcps项目有一个新的部分TypeSupport_Files。本部分执行opendds_idl 脚本以从我们的DDS数据类型生成TypeSupport文件。在这里,我们指示包含DDS数据类型的IDL文件,并指示从中生成的TypeSupport文件。

 TypeSupport_Files {
    StockQuoter.idl
  }

我们的IDL_Files部分包含原始IDL文件以及上一部分生成的TypeSupport IDL文件。

IDL_Files {
    StockQuoterTypeSupport.idl
    StockQuoter.idl
  }

在Header_Files和Source_Files部分包含opendds_idl-生成TypeSupport实现文件。MPC会自动添加生成的IDL存根和框架,因此我们不需要手动添加它们。

 Header_Files {
    StockQuoterTypeSupportImpl.h
  }

  Source_Files {
    StockQuoterTypeSupportImpl.cpp
  }
}

我们的发布者从上方使用StockQuoterCommon库,并添加publisher.cpp包含发布者的源文件main()。

project(*Publisher) : dcpsexe, svc_utils {
  after += *Common
  exename   = publisher

  includes += $(TAO_ROOT)/orbsvcs
  libs += StockQuoterCommon
  dynamicflags = STOCKQUOTERCOMMON_HAS_DLL

  TypeSupport_Files {
  }

  IDL_Files {
  }

  Header_Files {
  }

  Source_Files {
    publisher.cpp
  }

  Documentation_Files {
    README.txt
    domain_ids
  }
}

我们的订户还使用StockQuoterCommon库,添加了一个subscriber.cpp包含订户的main()和两个侦听器的源文件。

project(*Subscriber) : dcpsexe {
  after += *Common
  exename   = subscriber

  includes += $(TAO_ROOT)/orbsvcs
  libs += StockQuoterCommon
  dynamicflags = STOCKQUOTERCOMMON_HAS_DLL

  TypeSupport_Files {
  }

  IDL_Files {
  }

  Header_Files {
    QuoteDataReaderListenerImpl.h
  }

  Source_Files {
    QuoteDataReaderListenerImpl.cpp
    subscriber.cpp
  }

  Documentation_Files {
    README.txt
    domain_ids
  }
}

我们使用此MPC文件为我们的构建系统生成构建文件。例如,要生成GNU Makefile,我们执行

$ ACE_ROOT / bin / mwc.pl -type gnuace StockQuoter.mwc

为了生成Visual C ++ 7.1解决方案文件,我们执行

perl%ACE_ROOT%/ bin / mwc.pl -type vc71 StockQuoter.mwc

然后,我们构建项目。

目录

配置股票报价器

OpenDDS包含基于文件的配置机制。有了它,OpenDDS用户可以配置发布者或订阅者的传输,DCPSInfoRepo过程的位置以及许多其他设置。配置文件的语法类似于Windows INI文件的语法。它包含几个部分,而这些部分又包含类似属性的条目。基本语法如下:

[section1-name]
Attribute1=value1
Attribute2=value2

[section2-name]
Attribute1=value1
Attribute2=value2

《OpenDDS开发人员指南》的“配置”一章中介绍了完整的配置设置集。

我们基于TCP的示例dds_tcp_conf.ini对发布者和订阅者都使用一个配置文件:

dds_tcp_conf.ini # [common] # Debug Level DCPSDebugLevel=0 # IOR of DCPSInfoRepo process. DCPSInfoRepo=corbaloc::localhost:12345/DCPSInfoRepo # Sets the global transport configuration (used by default in the # process to config1, defined below DCPSGlobalTransportConfig=config1 # Transport configuration named config1, contains a single transport # instance named tcp1 (defined below) [config/config1] transports=tcp1 # Transport instance named tcp1, of type "tcp". Uses defaults for # all configuration paramaters. [transport/tcp1] transport_type=tcp

请注意,有三个部分,[common],[config/config1],和[transport/tcp1]。本[common] 节包含适用于整个过程的配置值。在此配置文件中,我们指定调试级别,该DCPSInfoRepo过程的对象引用以及全局传输配置。在这里,我们的DCPSInfoRepo 进程正在监听回送(127.0.0.1)接口,这意味着我们已将其配置为仅对在同一主机上运行的DDS进程可用。要使其在网络上可用,请使用IP地址或网络主机名代替localhost。我们已经指定config1 作为我们的全局传输配置,这意味着具有该名称的传输配置将被我们过程中所有未明确指定其他传输配置的读取器和写入器使用。

本[config/config1]节定义了名称为的传输配置config1。该transports选项指定 tcp1为此配置中包括的唯一传输实例。

本[transport/tcp1]节定义了一个名为的传输实例, tcp1并将其传输类型指定为tcp。如OpenDDS文档中所述,此部分还可以用于通过许多配置选项来配置传输。

目录

通过TCP传输运行股票报价器

要运行该示例,我们必须启动一个DCPSInfoRepo过程,并至少启动一个发布者和一个订阅者。要启动DCPSInfoRepo,我们使用以下命令行:

$ DDS_ROOT / bin / DCPSInfoRepo -ORBListenEndpoints iiop:// localhost:12345

我们的DCPSInfoRepo进程侦听端口12345。该端口与我们在DCPSInfoRepo上面的传输配置文件中的对象引用中指定的端口匹配。此DCPSInfoRepo 进程正在监听回送(127.0.0.1)接口,这意味着我们已将其配置为仅对在同一主机上运行的DDS进程可用。同样,要使其在网络上可用,请使用IP地址或网络主机名代替localhost。

我们有两个订阅者和一个发布者:

订户-DCPSConfigFile dds_tcp_conf.ini

订户-DCPSConfigFile dds_tcp_conf.ini

发布者-DCPSConfigFile dds_tcp_conf.ini

我们使用-DCPSConfigFile命令行参数来指示我们在上面创建的配置文件的名称。请注意,每个订阅者和发布者都使用相同的传输配置文件。

上面的命令行用于运行DCPSInfoRepo,带有内置主题的发布者和订阅者,默认情况下是内置主题。我们也可以关闭内置主题来运行这些过程。该-NOBITS 所使用的DCPSInfoRepo关闭内置的话题和"-DCPSBit 0" 所使用的其它应用程序DDS。命令行如下:

$ DDS_ROOT / bin / DCPSInfoRepo -NOBITS -ORBListenEndpoints iiop:// localhost:12345

订户-DCPS位0 -DCPSConfigFile dds_tcp_conf.ini

订户-DCPS位0 -DCPSConfigFile dds_tcp_conf.ini

发布者-DCPSBit 0 -DCPSConfigFile dds_tcp_conf.ini

发行者为SPY和MDY股票代号发布20个股票报价,每个订阅者都收到它们。发布者完成后,它将发布“ TRADING_CLOSED”消息,这将导致订阅者退出。

目录

通过UDP传输运行股票报价器

我们可以使用相同的代码库,通过简单地运行配置文件来运行UDP传输上的示例,该配置文件定义了一个指定UDP传输实例的全局传输配置。

这是dds_udp_conf.ini文件:

dds_udp_conf.ini # [common] # Debug Level DCPSDebugLevel=0 # IOR of DCPSInfoRepo process. DCPSInfoRepo=corbaloc::localhost:12345/DCPSInfoRepo # Sets the global transport configuration (used by default in the # process to config1, defined below DCPSGlobalTransportConfig=config1 # Transport configuration named config1, contains a single transport # instance named udp1 (defined below) [config/config1] transports=udp1 # Transport instance named udp1, of type "udp". Uses defaults for # all configuration paramaters.

[transport/udp1]
transport_type=udp

然后,我们DCPSInfoRepo像以前一样开始该过程:

$ DDS_ROOT / bin / DCPSInfoRepo -ORBListenEndpoints iiop:// localhost:12345

我们使用新的传输配置文件启动两个订户和发布者:

订户-DCPSConfigFile dds_udp_conf.ini

订户-DCPSConfigFile dds_udp_conf.ini

发布者-DCPSConfigFile dds_udp_conf.ini

我们还可以在关闭内置主题的情况下运行每个流程。命令行如下:

$ DDS_ROOT / bin / DCPSInfoRepo -NOBITS -ORBListenEndpoints iiop:// localhost:12345

订户-DCPS位0 -DCPSConfigFile dds_udp_conf.ini

订户-DCPS位0 -DCPSConfigFile dds_udp_conf.ini

发布者-DCPSBit 0 -DCPSConfigFile dds_udp_conf.ini

和以前一样,发布者为SPY和MDY报价器符号发布20个股票报价,每个订阅者都收到它们。发布者完成后,它将再次发布“ TRADING_CLOSED”消息,这将导致订阅者退出。唯一的区别是我们用UDP传输代替了TCP传输。更改运输方式无需更改代码。

目录

摘要

用于实时系统 的OMG数据分发服务(DDS) 是针对高性能,类型安全,发布和订阅通信中间件的规范。DDS解决了以数据为中心的应用程序,即那些对应用程序数据进行分发非常重要的应用程序。

OpenDDS是OMG数据分发服务规范的一个开源实现,为用户提供了一个有效的发布和订阅框架,并具有开源软件开发模型的优点。

OpenDDS包含基于文件的配置机制。通过配置文件,OpenDDS用户可以配置发布者或订阅者的传输,调试输出,内存分配,DCPSInfoRepo 代理进程的位置以及许多其他设置。在示例中我们已经显示,可以在不进行任何代码更改的情况下换出OpenDDS应用程序的基础传输。

参考资料

示例代码位于示例/ DCPS / IntroductionToOpenDDS的OpenDDS源代码分发中
用于实时系统的OMG数据分发服务(DDS)(https://www.omg.org/spec/DDS/)
OMG DDS门户(https://www.omg.org/spec/DDS/
OpenDDS主页(http://www.opendds.org
TAO开发人员指南主页(http://www.theaceorb.com/product/index.html
《 OpenDDS开发人员指南》(http://download.objectcomputing.com/OpenDDS/OpenDDS-latest.pdf
MPC(https://github.com/objectcomputing/MPC

OpenDDS官网文献:https://opendds.org/about/articles/Article-Intro.html

03-31
<think>好的,用户想了解OpenDDS的使用教程、下载和文档。我需要根据提供的引用内容来整理答案。首先,看看引用里提到的下载地址和文档信息。引用[1]和[4]提到了官方文档的下载链接,其中引用[4]还提到中文版开发指南。然后关于安装和使用,引用[2]和[3]提到了编译过程和构建系统,比如MPC和CMake。用户可能需要步骤指导,比如下载、编译、配置等。 接下来要分步骤回答,确保结构清晰。下载部分要给出链接,文档部分列出官方和中文版的资源。使用教程部分需要说明如何编译示例,可能需要提到具体的目录和make命令,如引用[2]中的例子。构建系统部分要解释MPC和CMake,引用[3]的内容。然后生成相关问题,可能涉及安装问题、跨平台支持、性能优化等。 需要检查所有引用是否准确对应到回答中,比如引用[5]看起来不太相关,可能是一个博客,但用户可能也需要社区资源。不过引用里没有更多有效信息,所以可能不提及。最后,确保使用正确的LaTeX格式,但用户的问题中没有数学内容,可能不需要。但按照系统指令,所有行内数学表达式需要用$...$,如果有的话。不过这里可能不涉及,所以主要注意结构、引用标注和中文回答。</think>以下是为您整理的OpenDDS使用指南,包含下载、文档和基础教程信息: ### 一、OpenDDS下载与文档 1. **官方下载地址** OpenDDS最新版本可从官网获取,支持与ACE/TAO框架集成: http://download.objectcomputing.com/OpenDDS/OpenDDS-latest.pdf [^1][^4] 2. **开发文档资源** - 英文文档:随安装包附带的`OpenDDS_dev_guide.pdf` - 中文版开发指南:覆盖3.23版本的配置与核心概念 --- ### 二、OpenDDS使用教程 #### 步骤1:编译示例程序 在安装完成后,可通过以下命令编译Messenger示例: ```bash cd OpenDDS-3.15/DevGuideExamples/DCPS/Messenger make ``` 编译后生成`publisher`和`subscriber`可执行文件[^2] #### 步骤2:构建系统选择 OpenDDS支持两种构建方式: - **MPC(Make Project Creator)**:OpenDDS原生采用的构建工具 - **CMake**:通用构建系统,适合复杂项目集成[^3] --- ### 三、关键配置注意事项 1. **IDL编译器调用** 通常通过构建系统自动处理,无需直接操作TAO IDL编译器 2. **跨平台兼容性** OpenDDS支持Windows/Linux/Unix,需确保ACE+TAO版本匹配 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值