DDS开源库FastDDS分析-helloworld程序

介绍

根据之前的文章,我们知道了,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模型。

DDS开源库FastDDS分析-helloworld程序_第1张图片

就像上图所示的样子,其实C/S架构也是可以实现的。

之所以想说上面的内容,其实主要还是,我自己对于传统的C/S架构很熟悉,所以开始接触pub-sub的时候总感觉怪怪的。但是,其实万变不离其宗,大家也都是差不多,只是换个皮。

接下来我们再来看代码吧,在生成的helloworld相关的代码中,也是对应的pub-sub代码。

idl定义

很简单的一个结构,主要就是定义一个string类型,先跑通整个流程。

struct HelloSeven
{
    string sevenData;
};

main

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

Publisher

对应生成的HelloSevenPublisher其实主要就是做了三件事:

  1. 初始化
  2. 启动等待订阅者
  3. 订阅者上线,发送数据

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主要做了如下的步骤:

  1. 创建participant
  2. 注册type
  3. 创建publisher
  4. 创建topic
  5. 创建writer
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;
    }
}

Subscriber

其实sub和pub的逻辑类似,但是生成的sub也有一些不同的地方。目前自动生成的代码中,sub主要做了如下的事情:

  1. 初始化
  2. 启动
  3. 订阅匹配
  4. 数据接收

初始化

初始化和pub的逻辑类似也是:

  1. 创建participant
  2. 注册type
  3. 创建subcriber
  4. 创建topic
  5. 创建reader
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:

DDS开源库FastDDS分析-helloworld程序_第2张图片

sub:

DDS开源库FastDDS分析-helloworld程序_第3张图片

你可能感兴趣的:(DDS分析,汽车,分布式,网络协议,物联网,系统安全)