• Opensplice的Topic简介


    在Overview中,简单介绍了一下Topic,下面详细介绍一下Topic。有不对的地方希望大家可以在留言区告诉我。

    Topic是由名称(name)、类型(type)以及服务(QoS)组成。

    1. Topic详解

    (1)Topic Types

    Opensplice支持不同语法的topic,比如:IDL,XML等。同时也支持一些其他vendor的topic,比如:PrismTech、Protobuf等。这里,我们还是只关注IDL。

    IDL的Primitive Types主要有:

    Table1 IDL Primitive Types
    Primitive Type  Size (bits)
    boolean  8
    octet
    char  8
    short  16
    unsigned short  16
    long  32
    unsigned long  32
    long long  64
    unsigned long long   64
     float 32 
     double  64

     在IDL中,没有int,这里的short、long以及long long相当于C99中的int16_t、int32_t以及int64_t。这里对于C++的转化基本遵循与IDL基本相同的类型,octet转换为char类型。

    IDL支持的template Types(个人理解主要的功能是为了兼容数据类型)主要由两种:

    Table 2 IDL Template Types
    Template Type Example
    string<length = UNBOUNDED$>

    string s1;
    string<32> s2;

    sequence<T,length = UNBOUNDED>

    sequence<octet> oseq;
    sequence<octet, 1024> oseq1k;
    sequence<MyType> mtseq;
    sequence<MyType, $10>$ mtseq10;

    IDL的string只能根据其最大长度进行参数化,然而sequence可以根据其最大长度以及其包含的类型进行参数化。sequence有些类似于C++中的vector。

    注意:如果没有提供最大长度,那么相应的类型存储空间为无穷,这样就意味着Opensplice会为这个类型尽可能多的分配存储空间。

    IDL支持的Constructed Types 类型主要由三种:

    Table 3 IDL Constructed Types 
    Constructed Types Examples
    enum enum Dimension {1D, 2D, 3D, 4D};
    struct

    struct Coord1D { long x;};
    struct Coord2D { long x; long y; };
    struct Coord3D { long x; long y; long z; };
    struct Coord4D { long x; long y; long z,
    unsigned long long t;};

    union

    union Coord switch (Dimension) {
    case 1D: Coord1D c1d;
    case 2D: Coord2D c2d;
    case 3D: Coord3D c3d;
    case 4D: Coord4D c4d;
    };

    这里应该说明一下,每个topic是一个struct类型,这个struct类型可以包含enum、struct、union类型,同样也可以包含primitive types与template types类型。同样也支持DDS支持的或者用户自定义的多维数组。

    (2)Topic Keys, Instances and Samples

    每个Topic相当于class(类),可以声明一个或若干个instance。Topic有其特殊的地方,Topic需要用key-set(由pragma keylist声明)来区分不同instances,比如下面代码中的DataCommType,用id作为key-set,每个Key-set可能为空,也可能包含由任意数量组成,为空的称为keyless topic,不为空的称为keyed topic(但key的类型存在一些限制:采用Primitive Types(见Table 1)、enum或者string。Key不能是Constructed Types、array或者sequence类型组成。),这两者之间由本质区别:

    -> keyless topic在定义的时候仅有一个instance,可以理解为唯一instance;

    -> key topic在定义的时候每个key-set对应一个instance。

    这里举一个key topic与没有属性的keyless topic

    module DataComm {
    struct DataCommType {
        short id;
        float data1;
        float data2;
      };
    #pragma keylist DataCommType id
    
    struct KeylessDataCommType {
        short id;
        float data1;
        float data2;
      };
    #pragma keylist KeylessDataCommType
    };

    上述两个topic由#pragma keylist定义的有key-set id的Topic,以及没有属性的key-set的topic。这两个Topic定义的代码可以表示为:

    dds::topic::Topic<DataComm::DataCommType> topic(dp2, "DDataComm");
    dds::topic::Topic<DataComm::KeylessDataCommType> klTopic(dp, "KLDataComm");

    上述例子中,topic对应不同的id产生不同的instance,而klTopic仅仅只有一个instance,不论id是否变化。

    Topic instances是运行时实体,DDS全程追踪全部实体是否满足:

    -> 存在writer;

    -> Topic instance是否在系统中是第一次运行;

    -> Topic instance是否在系统中移除。

    Topic instance影响这系统的reader,同时也影响着整个系统的存储结构。

    像我们刚才提起的两种topic的区别,在写一个keyless topic的时候实际上就是写唯一的一个instance(每次写只能修改唯一的topic),在写key topic的时候,可以针对不同的key-set修改不同instance。

    针对keyless topic的写操作的代码可以表示为:

    dds::pub::DataWriter<DataComm::KeylessDataCommType> kldw(pub, kltsTopic);
    DataComm::KeylessDataCommType klts(1, 26.0F, 70.0F);
    kldw.write(klts);
    kldw << DataComm::KeylessDataCommType(2, 26.0F, 70.0F);

    在这个代码的基础上,读操作的相应的顺序为图1所示。

     

                                                     图1 keyless topic的写入/读出顺序

    针对key topic 写操作的代码可以表示为:

    dds::pub::DataWriter<DataComm::DataCommType> dw(pub, topic);
    DataComm::DataCommType ts(1, 26.0F, 70.0F);
    dw.write(ts);
    dw << DataComm::DataCommType(2, 26.0F, 70.0F);

    在上述代码的基础上,读写顺序应为如图2所示。

                                                                       图2 key topic的写入/读出顺序

    总之,Topic可以理解为面向对象语言中的类,一个key代表一个instance。每个topic instance由Opensplice管理,且分配相应的内存资源。在实际应用中,每个topic instance对应一个硬件设备。在系统运行的过程中,每由一个设备接入,系统会监测到,同时为设个设备分配相应的topic instance。

    2. 范围信息(Scoping information)

    DDS为范围信息提供两种机制:域(Domain)和分区(Partition)。

    (1)域

    域建立一个虚拟网络,链接已加入它的所有DDS应用程序。 除非由用户应用程序明确调解,否则跨域不会发生任何通信。

    (2)分区

    域可以进一步组织成分区,其中每个分区可以表示topic的逻辑分组。

    DDS的分区由名字描述,比如:“ensorDataPartition, CommandPartition, LogDataPartition”。为了发布(publish)和订阅(subscribe)topic中的数据,分区必须显式的链接。

                                                                  图3 Domains and partitions in DDS

    DDS提供的用于加入分区的机制非常灵活,因为发布者或订阅者可以通过提供其全名来加入,比如:“SensorDataPartition”。或者可以加入所有匹配正则表达式的分区,例如Sens *或* Data *。支持的正则表达式符合POSIX标准。

    分区还可用于隔离主题实例。这里没有看到具体的例子暂时保留。

    3. 内容过滤(Content Filtering)

    内容的过滤可以约束创建的topic instance中的值。

    当订阅了(subscribe)content-filtered topic,在所有发布(publish)的消息中,只有符合topic滤波器的消息可以被接收。

    Table 3 Legal operators for DDS Filters and Query Conditions
    Constructed Type Example
    = equal
    <> not equal
    > greater than
    < less than
    >= greater than or equal
    <= less than or equal
    BETWEEN between and inclusive range
    LINK matches a string pattern

    Content-filtered topic是非常有用的:

    -> Content-filtered topic限制了使用内存的大小;

    -> 通过检查某些数据属性,可以使用过滤来简化应用程序。

    举个例子,在上节中的topic中,如果我们想得到data1在20.5~21.5之外,data2在30~50之外的数据,那么滤波器表达式可以为:

    ((data1 NOT BETWEEN 20.5 AND 21.5)
    OR
    (data2 NOT BETWEEN 30 AND 50))

    Content-filtered topic在代码中的建立如下所示:

    // Create the DataScope topic
    dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm");
    
    // Define the filter expression
    std::string expression =
    "(data1 NOT BETWEEN %20.5 AND %21.5) 
    OR 
    (data2 NOT BETWEEN %30 and %50)";
    
    // Define the filter parameters
    std::vector<std::string> params = {"20.5", "21.5", "30", "50"};
    
    // Create the filter for the content-filtered-topic
    dds::topic::Filter filter(expression, params);
    
    // Create the ContentFilteredTopic
    dds::topic::ContentFilteredTopic<DataComm::DataCommType> cfTopic(topic, "CFDDataComm", filter);
    dds::sub::Subscriber sub(dp);
    
    //This data reader will only receive data that matches the content filter
    dds::sub::DataReader<DataScope::DataScopeType> dr(sub, cfTopic);

     通过上述表达式,cfTopic仅接收data1在20.5~21.5之外,data2在30~50之外的数据。

    4. 程序代码

    keyless topic与content filter部分publish代码。

    // OpslPubkeyless.cpp
    
    #include <iostream>
    #include "gen/DataControl_DCPS.hpp"
    #include <thread>         // std::thread, std::this_thread::sleep_for
    #include <chrono> 
    #include "util.hpp"
    
    int main(int argc, char* argv[]) {
        if (argc < 2) {
            std::cout << "USAGE:
    	 tspub <sensor-id>" << std::endl;
            return -1;
        }
    
        int sid = atoi(argv[1]);
        const int N = 100;
    
        dds::domain::DomainParticipant dp(0);
        dds::topic::Topic<DataComm::KeylessDataCommType> kltopic(dp, "DataComm");
        dds::pub::Publisher pub(dp);
        dds::pub::DataWriter<DataComm::KeylessDataCommType> dw(pub, kltopic);
    
        const float avgT = 25;
        const float avgH = 0.6;
        const float deltaT = 5;
        const float deltaH = 0.15;
        // Initialize random number generation with a seed
        srandom(clock());
            
        // Write some temperature randomly changing around a set point
        float temp = avgT + ((random() * deltaT) / RAND_MAX);
        float hum  = avgH + ((random() * deltaH) / RAND_MAX);
        
        DataComm::KeylessDataCommType myData(sid, temp, hum);
    
        for (unsigned int i = 0; i < N; ++i) {
            dw.write(myData);
            std::cout << "DW << " << myData << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
            temp = avgT + ((random() * deltaT) / RAND_MAX);
            myData.data1(temp); 
            hum = avgH + ((random() * deltaH) / RAND_MAX);
            myData.data2(hum);
        }
        
        return 0;
    }

    keyless topic与content filter部分subscribe代码。

    // OpslSubkeyless.cpp
    
    #include <iostream>
    #include <algorithm>
    #include "gen/DataControl_DCPS.hpp"
    #include <thread>         // std::thread, std::this_thread::sleep_for
    #include <chrono> 
    #include "util.hpp"
    
    int main(int argc, char* argv[]) {
        if (argc < 2) {
            std::cout << "USAGE:
    	tssub <filter-expression>"
                    << std::endl;
            exit(-1);
        } 
    
        dds::domain::DomainParticipant dp(0);
        dds::topic::Topic<DataComm::KeylessDataCommType> kltopic(dp, "DataComm");
        dds::sub::Subscriber sub(dp);
        //dds::sub::DataReader<DataComm::KeylessDataCommType> dr(sub, kltopic);
    
        dds::topic::Filter filter(argv[1]);
        dds::topic::ContentFilteredTopic<DataComm::KeylessDataCommType> cfTopic(kltopic, "CFDataComm", filter);
        
        dds::sub::DataReader<DataComm::KeylessDataCommType> dr(sub, cfTopic);
    
        while (true) {
            auto samples = dr.read();
            //std::cout << samples->id() << " " << samples->data1() << " " << samples->data2() << std::endl;
            //std::cout << samples.data() << std::endl;
            std::for_each(samples.begin(),
                samples.end(),
                [](const dds::sub::Sample<DataComm::KeylessDataCommType>& s) {
                    std::cout << s.data() << std::endl;
                });
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        return 0;
    }

    将这两部分代码加入到上一章节的代码之中。修改上一章节的OpslSubkey.cpp,如下:

    // OpslSub.cpp
    
    #include <iostream>
    #include <algorithm>
    #include "gen/DataControl_DCPS.hpp"
    #include <thread>         // std::thread, std::this_thread::sleep_for
    #include <chrono> 
    #include "util.hpp"
    
    int main(int argc, char* argv[]) {
        if (argc < 2) {
            std::cout << "USAGE:
    	tssub <filter-expression>"
                    << std::endl;
            exit(-1);
        } 
    
        dds::domain::DomainParticipant dp(0);
        dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm");
        dds::sub::Subscriber sub(dp);
        
    
        dds::topic::Filter filter(argv[1]);
        dds::topic::ContentFilteredTopic<DataComm::DataCommType> cfTopic(topic,  "CFDataComm",  filter);
        dds::sub::DataReader<DataComm::DataCommType> dr(sub, cfTopic);
    
        while (true) {
            auto samples = dr.read();
            std::for_each(samples.begin(),
                samples.end(),
                [](const dds::sub::Sample<DataComm::DataCommType>& s) {
                    std::cout << s.data() << std::endl;
                });
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        return 0;
    }

    程序运行:

    -> keyless topic与key topic混合运行:

    ---> 如果OpslSub.cpp中的key topic的名称与OpslSubkeyless.cpp中的keyless topic的名字相同,那么在运行这两个程序时,会报运行时错误,如下:

    terminate called after throwing an instance of 'dds::core::Error'
      what():  Error: Failed to create Topic
    ========================================================================================
    Context     : dds::topic::detail::Topic<T>::Topic
    Date        : Thu Dec 13 07:52:48 CST 2018
    Node        : xxxx-GL552JX
    Process     : OpslSub <5848>
    Thread      : main thread 7efed7c99740
    Internals   : TTopicImpl.hpp/203/6.7.180404OSS
    ----------------------------------------------------------------------------------------
    Report      : Error: Failed to create Topic
    Internals   : dds::topic::detail::Topic<T>::Topic/TTopicImpl.hpp/203
    ----------------------------------------------------------------------------------------
    Report      : Create kernel entity failed. For Topic: <DDataComm>
    Internals   : u_topicNew/u_topic.c/235
    ----------------------------------------------------------------------------------------
    Report      : Precondition not met: Create Topic "DDataComm" failed: typename <DataComm::KeylessDataCommType> differs exiting definition <DataComm::DataCommType>.
    Internals   : v_topicNew/v_topicImpl.c/478

    Aborted (core dumped)

    所以在OpslPub.cpp中的key topic的名称为“DDataComm”,OpslPubkeyless.cpp中的keyless topic名称“DataComm”。

    ---> 如果将OpslSub.cpp中的key topic的名称为“DataComm”,OpslSubkeyless.cpp中的keyless topic名称“DDataComm”,那么在运行OpslPub.cpp与OpslPubkeyless.cpp之后,运行OpslSub.cpp会报运行时报和上面一样的错误。

    这是因为运行OpslSub.cpp之后,要接收的topic的类型与要接收相同名称的topic类型不匹配,所以产生了错误。

    -> keyless topic独立运行,且测试content fliter:

    ---> 如果运行多个OpslPubkeyless.cpp与OpslSubkeyless.cpp,且每个OpslSubkeyless.cpp运行独立content filter,例如: “data1 > 26 AND data2 > 0.65”。那么每个OpslPubkeyless.cpp发出的keyless topic都会被OpslSubkeyless.cpp接收到。从使用的情况看,与key topic没有区别

    -> 在运行程序时同样需要重针对keyless topic重载“<<”操作符。

    原创博文,转载请标明出处。

  • 相关阅读:
    Django-下载文件设置响应头和兼容中文
    django-strftime日期格式化问题
    API集成管理平台YAPI的搭建和使用
    dubbo 的 spi 思想是什么?
    关于
    Python接口自动化之unittest单元测试
    Python接口自动化之requests请求封装
    Python接口自动化之Token详解及应用
    Python接口自动化之cookie、session应用
    Python接口自动化-requests模块之post请求
  • 原文地址:https://www.cnblogs.com/hgl0417/p/12772205.html
Copyright © 2020-2023  润新知