FastDDS学习笔记之HelloWorld示例程序编译和运行

FastDDS学习笔记之HelloWorld示例程序编译和运行,第1张

目录

第一章:FastDDSclass="superseo">学习笔记之Ubuntu22上安装fastDDS环境
第二章:FastDDS学习笔记之HelloWorld示例程序编译和运行
第三章:FastDDS学习笔记之Fast-DDS-Gen安装记录

代码位置:~/Fast-DDS/Fast-DDS/examples/C++/HelloWorldExample

设置相应的环境变量

因为上一章节中,我们编译的库并没有放到系统中,而是放在了一个文件夹中,所以这里我们要先设定以下对应的环境变量:

export LD_LIBRARY_PATH=/home/xiaoqing/Fast-DDS/install/lib

这里的路径就是编译时候,填写的路径。

如果想要永久生效,执行以下命令:

echo 'export LD_LIBRARY_PATH=/home/xiaoqing/Fast-DDS/install/lib' >> ~/.bashrc

这里我是用的是永久生效的。

编译HelloWorld

在ubuntu中执行以下命令:

mkdir build && cd build
cmake .. -DCMAKE_PREFIX_PATH=~/Fast-DDS/install/

运行效果:

继续编译:

make

生成运行实例:

运行HelloWorld

接下来我们来运行HelloWorld,看一下效果。

新开一个终端,运行一个publisher:

./HelloWorldExample publisher

再新开一个终端,运行一个subscriber:

./HelloWorldExample subscriber

执行效果:
publisher:

subscriber:

源码分析

源码的文件目录:

HelloWorldExample
├── build
│  ├── CMakeCache.txt
│  ├── CMakeFiles
│  ├── cmake_install.cmake
│  ├── HelloWorldExample
│  └── Makefile
├── CMakeLists.txt
├── HelloWorld.cxx // 由idl生成
├── HelloWorld.h // 由idl生成
├── HelloWorld.idl      //idl 文件
├── HelloWorld_main.cpp
├── HelloWorldPublisher.cpp
├── HelloWorldPublisher.h
├── HelloWorldPubSubTypes.cxx // 由idl生成
├── HelloWorldPubSubTypes.h // 由idl生成
├── HelloWorldSubscriber.cpp
├── HelloWorldSubscriber.h
└── README.txt

代码中
HelloWorld.idl 定义payload,会生成HelloWorld.hHelloWorld.cxxHelloWorldPubSubTypes.hHelloWorldPubSubTypes.cxx四个文件,包含了序列化与反序列化的逻辑。

先来看一下HelloWorld.idl:

struct HelloWorld
{
	unsigned long index;
	string message;
};

只是简单地定义了下数据传输的结构体。

结构体包含一个id和一个字符串类型的message。

而这,也就是DDS概念模型中,TopicDataType的定义。

主题在概念上适合发布和订阅。

从OMG的DDS官网我们可以知道,IDL(DDS基于的是IDL4 v4.2标准)是一种不依赖于编程语言的,用于定义数据类型和接口的描述性语言。他同时也是OMG组织制定的标准。Fast DDS-Gen作为一个Java应用程序,就是用来解析idl文件,生成数据类型定义的(即HelloWorld.hHelloWorld.cxxHelloWorldPubSubTypes.hHelloWorldPubSubTypes.cxx)。

生成的文件HelloWorld.hHelloWorld.cxx`中会根据这个结构体生成类,并添加一些序列化使用的函数:

class HelloWorld
{
public:
    eProsima_user_DllExport HelloWorld& operator =(

            const HelloWorld& x);

    eProsima_user_DllExport HelloWorld& operator =(

            HelloWorld&& x);

    eProsima_user_DllExport bool operator ==(

            const HelloWorld& x) const;

    eProsima_user_DllExport bool operator !=(

            const HelloWorld& x) const;

    eProsima_user_DllExport void index(

            uint32_t _index);

    eProsima_user_DllExport uint32_t index() const;

    eProsima_user_DllExport uint32_t& index();

    eProsima_user_DllExport void message(

            const std::string& _message);

    eProsima_user_DllExport void message(

            std::string&& _message);

    eProsima_user_DllExport const std::string& message() const;

    eProsima_user_DllExport std::string& message();

    eProsima_user_DllExport static size_t getMaxCdrSerializedSize(

            size_t current_alignment = 0);

    eProsima_user_DllExport static size_t getCdrSerializedSize(

            const HelloWorld& data,

            size_t current_alignment = 0);

    eProsima_user_DllExport void deserialize(

            eprosima::fastcdr::Cdr& cdr);
    eProsima_user_DllExport static size_t getKeyMaxCdrSerializedSize(

            size_t current_alignment = 0);
    eProsima_user_DllExport static bool isKeyDefined();
    eProsima_user_DllExport void serializeKey(
            eprosima::fastcdr::Cdr& cdr) const;
private:
    uint32_t m_index;
    std::string m_message;
};

main函数中主要是根据参数创建相应的对象:

int main(int argc, char** argv)
{
    std::cout << "Starting "<< std::endl;
    int type = 1;
    int count = 10;
    long sleep = 100;
    if(argc > 1)
    {
        if(strcmp(argv[1],"publisher")==0)
        {
            type = 1;
            if (argc >= 3)
            {
                count = atoi(argv[2]);
                if (argc == 4)
                {
                    sleep = atoi(argv[3]);
                }
            }
        }
        else if(strcmp(argv[1],"subscriber")==0)
            type = 2;
    }
    else
    {
        std::cout << "publisher OR subscriber argument needed" << std::endl;
        Log::Reset();
        return 0;
    }
    switch(type)
    {
        case 1:
            {
                //创建publisher对象
                HelloWorldPublisher mypub;
                //初始化
                if(mypub.init())
                {
                    //运行publisher,发布10次
                    mypub.run(count, sleep);
                }
                break;
            }
        case 2:
            {
                //创建subscriber对象
                HelloWorldSubscriber mysub;
                //初始化
                if(mysub.init())
                {
                    //运行subscriber
                    mysub.run();
                }
                break;
            }
    }
    Domain::stopAll();
    Log::Reset();
    return 0;
}

真正的逻辑实现放在了HelloWorldPublisherHelloWorldSubscriber的实现中。

来看一下初始化部分。

HelloWorldPublisher流程分析

HelloWorldPublisherc初始化基本步骤:

  • 初始化m_Hello的内容,m_Hello即为需要发送的data
  • 初始化参与者的参数对象
  • 创建participant
  • 初始化writer
  • 创建publisher

代码分析如下:
publisher

bool HelloWorldPublisher::init()
{
    /*1. Init m_Hello */
    // 设置初始的index = 0
    m_Hello.index(0);
    // 设置初始值message
    m_Hello.message("HelloWorld");
    /*2. 初始化参与者的参数对象 */
    // 创建参与者的相关属性
    ParticipantAttributes PParam;
    PParam.rtps.builtin.discovery_config.discoveryProtocol = DiscoveryProtocol_t::SIMPLE;
    PParam.rtps.builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = true;
    PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriter = true;
    PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationWriterANDSubscriptionReader = true;
    PParam.rtps.builtin.discovery_config.leaseDuration = c_TimeInfinite;
    PParam.rtps.setName("Participant_pub");

    /*3. 创建participant */
    mp_participant = Domain::createParticipant(PParam);

    if (mp_participant == nullptr)
    {
        return false;

    }/*4. 注册数据类型 */
    Domain::registerType(mp_participant, &m_type);

    /*5. 初始化writer */
    PublisherAttributes Wparam;
    Wparam.topic.topicKind = NO_KEY;
    Wparam.topic.topicDataType = "HelloWorld";
    Wparam.topic.topicName = "HelloWorldTopic";
    Wparam.topic.historyQos.kind = KEEP_LAST_HISTORY_QOS;
    Wparam.topic.historyQos.depth = 30;
    Wparam.topic.resourceLimitsQos.max_samples = 50;
    Wparam.topic.resourceLimitsQos.allocated_samples = 20;
    Wparam.times.heartbeatPeriod.seconds = 2;
    Wparam.times.heartbeatPeriod.nanosec = 200 * 1000 * 1000;
    Wparam.qos.m_reliability.kind = RELIABLE_RELIABILITY_QOS;

    /*6. 创建publisher */
    mp_publisher = Domain::createPublisher(mp_participant, Wparam, (PublisherListener*)&m_listener);

    if (mp_publisher == nullptr)
    {
        return false;
    }
    return true;
}

其中m_Hello类型维HelloWorld, 调用index是设置其值:

void HelloWorld::index(uint32_t _index)
{
    m_index = _index;
}

然后开始执行其run函数:

void HelloWorldPublisher::run(uint32_t samples,uint32_t sleep)
{
    stop = false;
    /* 启动线程 */
    std::thread thread(&HelloWorldPublisher::runThread, this, samples, sleep);

    if (samples == 0)
    {
        std::cout << "Publisher running. Please press enter to stop the Publisher at any time." << std::endl;
        std::cin.ignore();
        stop = true;
    }
    else
    {
        std::cout << "Publisher running " << samples << " samples." << std::endl;
    }
    thread.join();
}

可以看出run函数中只是启动了个线程,接着便进入线程中:

void HelloWorldPublisher::runThread(uint32_t samples,uint32_t sleep)
{
    /* 这里samples传入值为10 */
    if (samples == 0)
    {
        while (!stop)
        {
            /* 执行函数publish */
            if (publish(false))
            {
                std::cout << "Message: " << m_Hello.message() << " with index: " << m_Hello.index() << " SENT" <<std::endl;

            }
            std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
        }
    }
    else
    {
        for (uint32_t i = 0; i < samples; ++i)
        {
            if (!publish())
            {
                --i;
            }
            else
            {
                std::cout << "Message: " << m_Hello.message() << " with index: " << m_Hello.index() << " SENT" <<std::endl;

            }
            std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
        }
    }
}

进入线程后主要是去执行了publish函数:

bool HelloWorldPublisher::publish(bool waitForListener)
{
    /* waitForListener传入为false */
    if (m_listener.firstConnected || !waitForListener || m_listener.n_matched > 0)
    {
        // 设定index的值,index初始化为0
        m_Hello.index(m_Hello.index() + 1);
        // 发布数据m_Hello
        mp_publisher->write((void*)&m_Hello);
        return true;
    }
    return false;
}

其中mp_publisher的类型是eprosima::fastrtps::Publisher.(头文件路径:Fast-DDS/include/fastrtps/publisher/Publisher.h)

write函数实现部分在代码:Fast-DDS/src/cpp/fastrtps_deprecated/publisher/Publisher.cpp

bool Publisher::write(void* Data)
{
    logInfo(PUBLISHER, "Writing new data");
    return mp_impl->create_new_change(ALIVE, Data);
}
HelloWorldsubscriber流程分析

HelloWorldSubscriber初始化基本步骤:

  • 初始化参与者的参数对象
  • 创建participant
  • 初始化reader
  • 创建publisher

代码分析如下

bool HelloWorldSubscriber::init()
{
    /*1. 初始化参与者的参数对象 */
    ParticipantAttributes PParam;
    PParam.rtps.builtin.discovery_config.discoveryProtocol = DiscoveryProtocol_t::SIMPLE;
    PParam.rtps.builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = true;
    PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriter = true;
    PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationWriterANDSubscriptionReader = true;
    PParam.rtps.builtin.discovery_config.leaseDuration = c_TimeInfinite;
    PParam.rtps.setName("Participant_sub");
    /*2. 创建参与者 */
    mp_participant = Domain::createParticipant(PParam);

    if (mp_participant == nullptr)
    {
        return false;
    }

    /*3. 注册数据类型 */
    Domain::registerType(mp_participant, &m_type);

    /*4. 初始化reader */
    SubscriberAttributes Rparam;
    Rparam.topic.topicKind = NO_KEY;
    Rparam.topic.topicDataType = "HelloWorld";
    Rparam.topic.topicName = "HelloWorldTopic";
    Rparam.topic.historyQos.kind = KEEP_LAST_HISTORY_QOS;
    Rparam.topic.historyQos.depth = 30;
    Rparam.topic.resourceLimitsQos.max_samples = 50;
    Rparam.topic.resourceLimitsQos.allocated_samples = 20;
    Rparam.qos.m_reliability.kind = RELIABLE_RELIABILITY_QOS;
    Rparam.qos.m_durability.kind = TRANSIENT_LOCAL_DURABILITY_QOS;
    /*4. 创建subscriber */
    mp_subscriber = Domain::createSubscriber(mp_participant, Rparam, (SubscriberListener*)&m_listener);
    if (mp_subscriber == nullptr)
    {
        return false;
    }
    return true;
}

继续看一下run函数:

void HelloWorldSubscriber::run()
{
    std::cout << "Subscriber running. Please press enter to stop the Subscriber" << std::endl;
    std::cin.ignore();
}

可以看出HelloWorldSubscriber的run函数中几乎什么都没做,只是等待一个按键后退出。

这里的主要读取数据的 *** 作是由init函数中注册的&m_listener来完成的。

m_listener的相关代码:

   class SubListener : public eprosima::fastrtps::SubscriberListener
    {
    public:
        SubListener(): n_matched(0), n_samples(0)
        {
        }
        ~SubListener()
        {
        }
        void onSubscriptionMatched(eprosima::fastrtps::Subscriber* sub, eprosima::fastrtps::rtps::MatchingInfo& info);
        void onNewDataMessage(eprosima::fastrtps::Subscriber* sub);
        HelloWorld m_Hello;
        eprosima::fastrtps::SampleInfo_t m_info;
        int n_matched;
        uint32_t n_samples;
    } m_listener;

SubscriberListener的代码路径是Fast-DDS/include/fastrtps/subscriber/SubscriberListener.h

class RTPS_DllAPI SubscriberListener
{
    public:
        SubscriberListener(){}
        virtual ~SubscriberListener(){}
        /**
         * 由用户实现的功能,包含接收到新数据消息时要执行的 *** 作。
         */
        virtual void onNewDataMessage(Subscriber* sub)
        {
            (void)sub;
        }
        /**
         * Virtual 当订阅者与新的 Writer 匹配(或不匹配)时调用的方法。
         */
        virtual void onSubscriptionMatched(Subscriber* sub,rtps::MatchingInfo& info)
        {
            (void)sub;
            (void)info;
        }
        /**
         * 当主题错过最后期限时调用的方法
         */
        virtual void on_requested_deadline_missed(Subscriber* sub,const RequestedDeadlineMissedStatus& status)
        {
            (void)sub;
            (void)status;
        }
        /**
         * 当与订阅服务器关联的活动状态更改时调用的方法
         */
        virtual void on_liveliness_changed(Subscriber* sub,const LivelinessChangedStatus& status){

            (void)sub;
            (void)status;
        }
};

可以看出onNewDataMessage函数是消息相应的函数:

void HelloWorldSubscriber::SubListener::onNewDataMessage(Subscriber* sub)
{
    if (sub->takeNextData((void*)&m_Hello, &m_info))
    {
        if (m_info.sampleKind == ALIVE)
        {
            this->n_samples++;
            // Print your structure data here.
            std::cout << "Message " << m_Hello.message() << " " << m_Hello.index() << " RECEIVED" << std::endl;
        }
    }
}

这里的实现就是获取数据后并打印。

sub的类型为eprosima::fastrtps::Subscriber*(头文件路径:Fast-DDS/include/fastrtps/subscriber/Subscriber.h).

其中定义了``函数:

    /**
      * @brief 从订阅者处获取下一个样本。 从订户中删除样本。
      * @param sample 指向您希望存储样本的对象的指针。
      * @param info 指向 SampleInfo_t 结构的指针,该结构通知您有关您的样本。
      * @return 如果取样则为真。
      * @note 该方法被阻塞一段时间。
      * SubscriberAttributes 上的 ReliabilityQosPolicy.max_blocking_time 定义了这段时间。
      */
    bool takeNextData(void* sample,SampleInfo_t* info);

好了今天的分享就到这里,明天继续。_

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/920676.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存