数据分发服务 (DDS)是一种以 数据为中心的通信协议,用于分布式软件应用程序通信。它描述了支持数据提供者和数据消费者之间通信的通信应用程序编程接口 (API) 和通信语义。
由于它是一个以数据为中心的发布订阅 (DCPS) 模型,因此在其实现中定义了三个关键应用实体:发布实体,定义信息生成对象及其属性;订阅实体,它定义了信息消费对象及其属性;和配置实体,定义作为主题传输的信息类型,并使用其服务质量 (QoS) 属性创建发布者和订阅者,确保上述实体的正确性能。
DDS 使用 QoS 来定义 DDS 实体的行为特征。QoS 由单独的 QoS 策略(源自 QoSPolicy 的类型的对象)组成。这些在Policy中描述。
在 DCPS 模型中,为开发通信应用系统定义了四个基本要素。
出版商。它是负责创建和配置其实现的DataWriters的 DCPS 实体。DataWriter是负责实际发布消息的实体。每个人都有一个分配的主题,在该主题下发布消息。有关详细信息,请参阅发布者。
订户。它是 DCPS 实体,负责接收在其订阅的主题下发布的数据。它为一个或多个DataReader对象提供服务,这些对象负责将新数据的可用性传达给应用程序。有关详细信息,请参阅订阅者。
主题。它是绑定发布和订阅的实体。它在 DDS 域中是唯一的。通过TopicDescription,它允许发布和订阅的数据类型的统一。有关详细信息,请参阅主题。
域。这是用于链接所有发布者和订阅者的概念,属于一个或多个应用程序,它们在不同主题下交换数据。这些参与域的单个应用程序称为DomainParticipant。DDS 域由域 ID 标识。DomainParticipant 定义域 ID 以指定它所属的 DDS 域。具有不同 ID 的两个 DomainParticipants 不知道彼此在网络中的存在。因此,可以创建多个通信通道。这适用于涉及多个DDS应用程序的场景,它们各自的DomainParticipants相互通信,但这些应用程序不得干扰。域参与者充当其他 DCPS 实体的容器,充当 发布者、订阅者和主题实体的工厂,并在域中提供管理服务。有关详细信息,请参阅域。
为支持 DDS 应用程序而开发的实时发布订阅 (RTPS)协议是一种发布订阅通信中间件,它通过 UDP/IP 等尽力传输传输。此外,Fast DDS 还支持 TCP 和共享内存 (SHM) 传输。
它旨在支持单播和多播通信。
在继承自 DDS 的 RTPS 顶部,可以找到域,它定义了一个单独的通信平面。几个域可以同时独立地共存。一个域包含任意数量的RTPSParticipants,即能够发送和接收数据的元素。为此,RTPSParticipants 使用他们的Endpoints:
RTPSWriter:能够发送数据的端点。
RTPSReader:能够接收数据的端点。
RTPSParticipant 可以有任意数量的写入器和读取器端点。
通信围绕主题进行,主题定义和标记正在交换的数据。主题不属于特定参与者。参与者通过 RTPSWriters 对主题下发布的数据进行更改,并通过 RTPSReaders 接收与其订阅的主题相关的数据。通信单元称为Change,它表示在 Topic 下写入的数据的更新。 RTPSReaders/RTPSWriters在其History上注册这些更改,这是一种用作最近更改缓存的数据结构。
在eProsima Fast DDS的默认配置中,当您通过 RTPSWriter 端点发布更改时,会在后台执行以下步骤:
更改将添加到 RTPSWriter 的历史缓存中。
RTPSWriter 将更改发送到它知道的任何 RTPSReaders。
接收到数据后,RTPSReaders 用新的变化更新他们的历史缓存。
但是,Fast DDS 支持多种配置,允许您更改 RTPSWriters/RTPSReaders 的行为。修改 RTPS 实体的默认配置意味着 RTPSWriters 和 RTPSReaders 之间的数据交换流发生变化。此外,通过选择服务质量 (QoS) 策略,您可以通过多种方式影响这些历史缓存的管理方式,但通信循环保持不变。您可以继续阅读RTPS 层部分,了解更多关于快速 DDS 中 RTPS 协议的实现。
DDS 是实现 DCPS 模型的以数据为中心的通信中间件。该模型基于发布者的开发,这是一个数据生成元素;和一个订阅者,一个数据消费元素。这些实体通过主题进行通信,主题是绑定两个 DDS 实体的元素。发布者在主题下生成信息,订阅者订阅同一主题以接收信息
首先,您需要按照安装手册中列出的步骤安装 eProsima Fast DDS及其所有依赖项。您还需要完成安装手册中列出的安装 eProsima Fast DDS-Gen工具的步骤。此外,本教程中提供的所有命令都针对 Linux 环境进行了概述。
DDS 应用程序需要 Fast DDS 和 Fast CDR 库。根据安装过程,使这些库可用于我们的 DDS 应用程序的过程将略有不同。在 Linux 上,可以在目录/usr/include/fastrtps/和 /usr/include/fastcdr/中分别找到 Fast DDS 和 Fast CDR 的头文件。两者的编译库都可以在目录/usr/lib/中找到
cmake_minimum_required(VERSION 3.12.4)
if(NOT CMAKE_VERSION VERSION_LESS 3.0)
cmake_policy(SET CMP0048 NEW)
endif()
project(DDSHelloWorld)
# Find requirements
if(NOT fastcdr_FOUND)
find_package(fastcdr REQUIRED)
endif()
if(NOT fastrtps_FOUND)
find_package(fastrtps REQUIRED)
endif()
# Set C++11
include(CheckCXXCompilerFlag)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR
CMAKE_CXX_COMPILER_ID MATCHES "Clang")
check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)
if(SUPPORTS_CXX11)
add_compile_options(-std=c++11)
else()
message(FATAL_ERROR "Compiler doesn't support C++11")
endif()
endif()
IDL通过生成的数据文件为:
这必须生成以下文件:
HelloWorld.cxx:HelloWorld 类型定义。
HelloWorld.h:HelloWorld.cxx 的头文件。
HelloWorldPubSubTypes.cxx:HelloWorld 类型的序列化和反序列化代码。
HelloWorldPubSubTypes.h:HelloWorldPubSubTypes.cxx 的头文件。
// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @file HelloWorldPublisher.cpp
17 *
18 */
19
20#include "HelloWorldPubSubTypes.h"
21
22#include
23#include
24#include
25#include
26#include
27#include
28
29using namespace eprosima::fastdds::dds;
30
31class HelloWorldPublisher
32{
33private:
34
35 HelloWorld hello_;
36
37 DomainParticipant* participant_;
38
39 Publisher* publisher_;
40
41 Topic* topic_;
42
43 DataWriter* writer_;
44
45 TypeSupport type_;
46
47 class PubListener : public DataWriterListener
48 {
49 public:
50
51 PubListener()
52 : matched_(0)
53 {
54 }
55
56 ~PubListener() override
57 {
58 }
59
60 void on_publication_matched(
61 DataWriter*,
62 const PublicationMatchedStatus& info) override
63 {
64 if (info.current_count_change == 1)
65 {
66 matched_ = info.total_count;
67 std::cout << "Publisher matched." << std::endl;
68 }
69 else if (info.current_count_change == -1)
70 {
71 matched_ = info.total_count;
72 std::cout << "Publisher unmatched." << std::endl;
73 }
74 else
75 {
76 std::cout << info.current_count_change
77 << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
78 }
79 }
80
81 std::atomic_int matched_;
82
83 } listener_;
84
85public:
86
87 HelloWorldPublisher()
88 : participant_(nullptr)
89 , publisher_(nullptr)
90 , topic_(nullptr)
91 , writer_(nullptr)
92 , type_(new HelloWorldPubSubType())
93 {
94 }
95
96 virtual ~HelloWorldPublisher()
97 {
98 if (writer_ != nullptr)
99 {
100 publisher_->delete_datawriter(writer_);
101 }
102 if (publisher_ != nullptr)
103 {
104 participant_->delete_publisher(publisher_);
105 }
106 if (topic_ != nullptr)
107 {
108 participant_->delete_topic(topic_);
109 }
110 DomainParticipantFactory::get_instance()->delete_participant(participant_);
111 }
112
113 //!Initialize the publisher
114 bool init()
115 {
116 hello_.index(0);
117 hello_.message("HelloWorld");
118
119 DomainParticipantQos participantQos;
120 participantQos.name("Participant_publisher");
121 participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);
122
123 if (participant_ == nullptr)
124 {
125 return false;
126 }
127
128 // Register the Type
129 type_.register_type(participant_);
130
131 // Create the publications Topic
132 topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);
133
134 if (topic_ == nullptr)
135 {
136 return false;
137 }
138
139 // Create the Publisher
140 publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);
141
142 if (publisher_ == nullptr)
143 {
144 return false;
145 }
146
147 // Create the DataWriter
148 writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_);
149
150 if (writer_ == nullptr)
151 {
152 return false;
153 }
154 return true;
155 }
156
157 //!Send a publication
158 bool publish()
159 {
160 if (listener_.matched_ > 0)
161 {
162 hello_.index(hello_.index() + 1);
163 writer_->write(&hello_);
164 return true;
165 }
166 return false;
167 }
168
169 //!Run the Publisher
170 void run(
171 uint32_t samples)
172 {
173 uint32_t samples_sent = 0;
174 while (samples_sent < samples)
175 {
176 if (publish())
177 {
178 samples_sent++;
179 std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
180 << " SENT" << std::endl;
181 }
182 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
183 }
184 }
185};
186
187int main(
188 int argc,
189 char** argv)
190{
191 std::cout << "Starting publisher." << std::endl;
192 int samples = 10;
193
194 HelloWorldPublisher* mypub = new HelloWorldPublisher();
195 if(mypub->init())
196 {
197 mypub->run(static_cast(samples));
198 }
199
200 delete mypub;
201 return 0;
202}
每一部分的功能:
DomainParticipantFactory. 允许创建和销毁 DomainParticipant 对象。
DomainParticipant. 充当所有其他实体对象的容器以及发布者、订阅者和主题对象的工厂。
TypeSupport. 为参与者提供序列化、反序列化和获取特定数据类型的密钥的功能。
Publisher. 它是负责创建 DataWriters 的对象。
DataWriter. 允许应用程序设置要在给定主题下发布的数据的值。
DataWriterListener. 允许重新定义 DataWriterListener 的功能。
派生类的私有成员
类的私有数据成员,hello_数据成员被定义为 HelloWorld类的一个对象,它定义了我们用 IDL 文件创建的数据类型。接下来定义参与者、发布者、主题、DataWriter和数据类型对应的私有数据成员。类的type_对象TypeSupport是将用于在 DomainParticipant 中注册主题数据类型的对象
PubListener通过从该类继承来定义DataWriterListener该类。此类覆盖默认的 DataWriter 侦听器回调,允许在发生事件时执行例程。当检测到新的 DataReader 正在侦听 DataWriter 正在发布的主题时,重写的回调on_publication_matched() 允许定义一系列操作。检测与 DataWriter 匹配的 DataReader的info.current_count_change()这些更改。这是MatchedStatus允许跟踪订阅状态更改的结构中的成员。最后,listener_类的对象被定义为 的实例PubListener
类的公共构造函数和析构函数HelloWorldPublisher定义如下。构造函数将类的私有数据成员初始化nullptr为 ,TypeSupport 对象除外,它被初始化为HelloWorldPubSubType类的实例。类析构函数删除这些数据成员,从而清理系统内存
初始化 HelloWorld 类型hello_结构成员的内容。
通过 DomainParticipant 的 QoS 为参与者分配名称。
使用DomainParticipantFactory创建参与者。
注册 IDL 中定义的数据类型。
为出版物创建主题。
创建发布者。
使用先前创建的侦听器创建 DataWriter。
为了发布,实现了公共成员功能publish()。在 DataWriter 的侦听器回调中,表明 DataWriter 已与侦听发布主题的 DataReader 匹配,数据成员matched_被更新。它包含发现的 DataReader 的数量。因此,当发现第一个 DataReader 时,应用程序开始发布。这只是由 DataWriter 对象写入更改
// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @file HelloWorldSubscriber.cpp
17 *
18 */
19
20#include "HelloWorldPubSubTypes.h"
21
22#include
23#include
24#include
25#include
26#include
27#include
28#include
29#include
30
31using namespace eprosima::fastdds::dds;
32
33class HelloWorldSubscriber
34{
35private:
36
37 DomainParticipant* participant_;
38
39 Subscriber* subscriber_;
40
41 DataReader* reader_;
42
43 Topic* topic_;
44
45 TypeSupport type_;
46
47 class SubListener : public DataReaderListener
48 {
49 public:
50
51 SubListener()
52 : samples_(0)
53 {
54 }
55
56 ~SubListener() override
57 {
58 }
59
60 void on_subscription_matched(
61 DataReader*,
62 const SubscriptionMatchedStatus& info) override
63 {
64 if (info.current_count_change == 1)
65 {
66 std::cout << "Subscriber matched." << std::endl;
67 }
68 else if (info.current_count_change == -1)
69 {
70 std::cout << "Subscriber unmatched." << std::endl;
71 }
72 else
73 {
74 std::cout << info.current_count_change
75 << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
76 }
77 }
78
79 void on_data_available(
80 DataReader* reader) override
81 {
82 SampleInfo info;
83 if (reader->take_next_sample(&hello_, &info) == ReturnCode_t::RETCODE_OK)
84 {
85 if (info.valid_data)
86 {
87 samples_++;
88 std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
89 << " RECEIVED." << std::endl;
90 }
91 }
92 }
93
94 HelloWorld hello_;
95
96 std::atomic_int samples_;
97
98 } listener_;
99
100public:
101
102 HelloWorldSubscriber()
103 : participant_(nullptr)
104 , subscriber_(nullptr)
105 , topic_(nullptr)
106 , reader_(nullptr)
107 , type_(new HelloWorldPubSubType())
108 {
109 }
110
111 virtual ~HelloWorldSubscriber()
112 {
113 if (reader_ != nullptr)
114 {
115 subscriber_->delete_datareader(reader_);
116 }
117 if (topic_ != nullptr)
118 {
119 participant_->delete_topic(topic_);
120 }
121 if (subscriber_ != nullptr)
122 {
123 participant_->delete_subscriber(subscriber_);
124 }
125 DomainParticipantFactory::get_instance()->delete_participant(participant_);
126 }
127
128 //!Initialize the subscriber
129 bool init()
130 {
131 DomainParticipantQos participantQos;
132 participantQos.name("Participant_subscriber");
133 participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);
134
135 if (participant_ == nullptr)
136 {
137 return false;
138 }
139
140 // Register the Type
141 type_.register_type(participant_);
142
143 // Create the subscriptions Topic
144 topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);
145
146 if (topic_ == nullptr)
147 {
148 return false;
149 }
150
151 // Create the Subscriber
152 subscriber_ = participant_->create_subscriber(SUBSCRIBER_QOS_DEFAULT, nullptr);
153
154 if (subscriber_ == nullptr)
155 {
156 return false;
157 }
158
159 // Create the DataReader
160 reader_ = subscriber_->create_datareader(topic_, DATAREADER_QOS_DEFAULT, &listener_);
161
162 if (reader_ == nullptr)
163 {
164 return false;
165 }
166
167 return true;
168 }
169
170 //!Run the Subscriber
171 void run(
172 uint32_t samples)
173 {
174 while(listener_.samples_ < samples)
175 {
176 std::this_thread::sleep_for(std::chrono::milliseconds(100));
177 }
178 }
179};
180
181int main(
182 int argc,
183 char** argv)
184{
185 std::cout << "Starting subscriber." << std::endl;
186 int samples = 10;
187
188 HelloWorldSubscriber* mysub = new HelloWorldSubscriber();
189 if(mysub->init())
190 {
191 mysub->run(static_cast(samples));
192 }
193
194 delete mysub;
195 return 0;
196}
Subscriber. 它是负责创建和配置 DataReader 的对象。
DataReader. 它是负责实际接收数据的对象。它在应用程序中注册标识要读取的数据的主题(TopicDescription)并访问订阅者接收到的数据。
DataReaderListener. 这是分配给数据读取器的侦听器。
DataReaderQoS. 定义 DataReader 的 QoS 的结构。
SampleInfo. 它是伴随每个样本“读取”或“获取”的信息。
从类的私有数据成员开始,值得一提的是数据读取监听器的实现。类的私有数据成员将是参与者、订阅者、主题、数据读取器和数据类型。与数据写入器的情况一样,侦听器实现了要在事件发生时执行的回调。SubListener 的第一个被覆盖的回调是on_subscription_matched(),它类似于on_publication_matched()DataWriter 的回调
void on_subscription_matched(
DataReader*,
const SubscriptionMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
std::cout << "Subscriber unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
}
}
第二个被覆盖的回调是on_data_available(). 在此,数据读取器可以访问的下一个接收到的样本被获取并处理以显示其内容。在这里SampleInfo定义了类的对象,它决定了一个样本是否已经被读取或获取。每次读取样本时,接收样本的计数器都会增加
void on_data_available(
DataReader* reader) override
{
SampleInfo info;
if (reader->take_next_sample(&hello_, &info) == ReturnCode_t::RETCODE_OK)
{
if (info.valid_data)
{
samples_++;
std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
<< " RECEIVED." << std::endl;
}
}
}