根据之前的文章,我们知道了,gen工具生成的代码结构,这篇文章主要就是针对具体的代码做分析。
其实根据官方文档的介绍,dds主要是用来传输数据的,所以整体的设计也是以数据作为核心来设计的。
例如pub-sub模型,其实就是一个发布数据,一个订阅数据。
这里和我们传统的C/S架构还是有些区别的,不过也可以做一个类比。
传统的C/S架构中,server端主要是提供服务,而对应到pub-sub模型,其实也可以认为pub就是提供服务的一方。
而如果想要DDS来实现我们传统的C/S模型,因为pub-sub是一个单向的数据传输模式,即数据由pub产生并流向sub。但是在C/S中,client和server是由交互的,数据是互相传输的。所以DDS想要实现C/S模型,在代码开发的时候,其实可以在pub-sub之上,建立一个C/S模型。
就像上图所示的样子,其实C/S架构也是可以实现的。
之所以想说上面的内容,其实主要还是,我自己对于传统的C/S架构很熟悉,所以开始接触pub-sub的时候总感觉怪怪的。但是,其实万变不离其宗,大家也都是差不多,只是换个皮。
接下来我们再来看代码吧,在生成的helloworld相关的代码中,也是对应的pub-sub代码。
很简单的一个结构,主要就是定义一个string类型,先跑通整个流程。
struct HelloSeven
{
string sevenData;
};
HelloSevenPubSubMain.cxx
main函数内部其实主要实现的就是一个参数判断,根据不同的参数去启动对应的publisher还是subscriber。
// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*!
* @file HelloSevenPubSubMain.cpp
* This file acts as a main entry point to the application.
*
* This file was generated by the tool fastcdrgen.
*/
#include "HelloSevenPublisher.h"
#include "HelloSevenSubscriber.h"
int main(
int argc,
char** argv)
{
int type = 0;
if (argc == 2)
{
if (strcmp(argv[1], "publisher") == 0)
{
type = 1;
}
else if (strcmp(argv[1], "subscriber") == 0)
{
type = 2;
}
}
if (type == 0)
{
std::cout << "Error: Incorrect arguments." << std::endl;
std::cout << "Usage: " << std::endl << std::endl;
std::cout << argv[0] << " publisher|subscriber" << std::endl << std::endl;
return 0;
}
std::cout << "Starting " << std::endl;
// Register the type being used
switch (type)
{
case 1:
{
HelloSevenPublisher mypub;
if (mypub.init())
{
mypub.run();
}
break;
}
case 2:
{
HelloSevenSubscriber mysub;
if (mysub.init())
{
mysub.run();
}
break;
}
}
return 0;
}
对应生成的HelloSevenPublisher其实主要就是做了三件事:
HelloSevenPublisher定义:
class HelloSevenPublisher
{
public:
HelloSevenPublisher();
virtual ~HelloSevenPublisher();
bool init();
void run();
private:
eprosima::fastdds::dds::DomainParticipant* participant_;
eprosima::fastdds::dds::Publisher* publisher_;
eprosima::fastdds::dds::Topic* topic_;
eprosima::fastdds::dds::DataWriter* writer_;
eprosima::fastdds::dds::TypeSupport type_;
class PubListener : public eprosima::fastdds::dds::DataWriterListener
{
public:
PubListener() = default;
~PubListener() override = default;
void on_publication_matched(
eprosima::fastdds::dds::DataWriter* writer,
const eprosima::fastdds::dds::PublicationMatchedStatus& info) override;
int matched = 0;
}
listener_;
};
初始化主要是在init函数里面进行的。
init主要做了如下的步骤:
bool HelloSevenPublisher::init()
{
/* Initialize data_ here */
//CREATE THE PARTICIPANT
//创建对应的participant
DomainParticipantQos pqos;
pqos.name("Participant_pub");
participant_ = DomainParticipantFactory::get_instance()->create_participant(0, pqos);
if (participant_ == nullptr)
{
return false;
}
//REGISTER THE TYPE
//注册type
type_.register_type(participant_);
//CREATE THE PUBLISHER
//创建publisher
publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);
if (publisher_ == nullptr)
{
return false;
}
//CREATE THE TOPIC
//创建对应的topic
topic_ = participant_->create_topic(
"HelloSevenTopic",
type_.get_type_name(),
TOPIC_QOS_DEFAULT);
if (topic_ == nullptr)
{
return false;
}
// CREATE THE WRITER
//创建writer
writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_);
if (writer_ == nullptr)
{
return false;
}
std::cout << "HelloSeven DataWriter created." << std::endl;
return true;
}
启动阶段其实很简单,就是写了一个循环,检查listner_.matched,如果没有匹配到sub,就继续等待。
匹配到sub的话,就会发送对应的"hello seven!!!"
void HelloSevenPublisher::run()
{
std::cout << "HelloSeven DataWriter waiting for DataReaders." << std::endl;
while (listener_.matched == 0)
{
std::this_thread::sleep_for(std::chrono::milliseconds(250)); // Sleep 250 ms
}
// Publication code
HelloSeven st;
st.sevenData(std::string("hello seven!!!"));
/* Initialize your structure here */
int msgsent = 0;
char ch = 'y';
do
{
if (ch == 'y')
{
writer_->write(&st);
++msgsent;
std::cout << "Sending sample, count=" << msgsent << ", send another sample?(y-yes,n-stop): ";
}
else if (ch == 'n')
{
std::cout << "Stopping execution " << std::endl;
break;
}
else
{
std::cout << "Command " << ch << " not recognized, please enter \"y/n\":";
}
} while (std::cin >> ch);
}
这里主要是实现对应listener的回调函数,当sub匹配到时,会调用到这里。
void HelloSevenPublisher::PubListener::on_publication_matched(
eprosima::fastdds::dds::DataWriter*,
const eprosima::fastdds::dds::PublicationMatchedStatus& info)
{
if (info.current_count_change == 1)
{
matched = info.total_count;
std::cout << "DataWriter matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched = info.total_count;
std::cout << "DataWriter unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for PublicationMatchedStatus current count change" << std::endl;
}
}
其实sub和pub的逻辑类似,但是生成的sub也有一些不同的地方。目前自动生成的代码中,sub主要做了如下的事情:
初始化和pub的逻辑类似也是:
bool HelloSevenSubscriber::init()
{
//CREATE THE PARTICIPANT
//创建participant
DomainParticipantQos pqos;
pqos.name("Participant_sub");
participant_ = DomainParticipantFactory::get_instance()->create_participant(0, pqos);
if (participant_ == nullptr)
{
return false;
}
//REGISTER THE TYPE
//注册type
type_.register_type(participant_);
//CREATE THE SUBSCRIBER
//创建subcriber
subscriber_ = participant_->create_subscriber(SUBSCRIBER_QOS_DEFAULT, nullptr);
if (subscriber_ == nullptr)
{
return false;
}
//CREATE THE TOPIC
//创建topic
topic_ = participant_->create_topic(
"HelloSevenTopic",
type_.get_type_name(),
TOPIC_QOS_DEFAULT);
if (topic_ == nullptr)
{
return false;
}
//CREATE THE READER
//创建reader
DataReaderQos rqos = DATAREADER_QOS_DEFAULT;
rqos.reliability().kind = RELIABLE_RELIABILITY_QOS;
reader_ = subscriber_->create_datareader(topic_, rqos, &listener_);
if (reader_ == nullptr)
{
return false;
}
return true;
}
启动函数其实没做啥,主要就是打印一些内容
void HelloSevenSubscriber::run()
{
std::cout << "Waiting for Data, press Enter to stop the DataReader. " << std::endl;
std::cin.ignore();
std::cout << "Shutting down the Subscriber." << std::endl;
}
匹配和pub的逻辑类似,匹配上之后会去设置matched值。
void HelloSevenSubscriber::SubListener::on_subscription_matched(
DataReader*,
const SubscriptionMatchedStatus& info)
{
if (info.current_count_change == 1)
{
matched = info.total_count;
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched = info.total_count;
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;
}
}
void HelloSevenSubscriber::SubListener::on_data_available(
DataReader* reader)
{
// Take data
HelloSeven st;
SampleInfo info;
if (reader->take_next_sample(&st, &info) == ReturnCode_t::RETCODE_OK)
{
if (info.valid_data)
{
// Print your structure data here.
++samples;
std::cout << "HelloSeven received, data: " << st.sevenData() << std::endl;
std::cout << "Sample received, count=" << samples << std::endl;
}
}
}
其实整体生成的代码还是比较简单的,因为DDS主要还是围绕data来进行的,所以我们在定义好我们自己需要传输的数据结构,通过编写idl,然后用fastddsgen工具就可以生成对应的代码,进行通信了。
所以如果实际场景中,仅仅只有数据的单向流动的话,那么通过gen工具生成的对应代码已经完全能满足对应需求了。
但是如果想要实现更加复杂的业务,那么就需要在此基础上继续开发了。
pub:
sub: