fastdds:DataWriter和DataReader匹配规则

fastdds中的函数EDP::valid_matching对DataWriter和DataReader进行匹配,需要判断的匹配项包括topic、data type、qos策略,当所有判断均通过后,DataWriter和DataReader方可相互发现;否则,不能相互发现。

cpp 复制代码
bool EDP::valid_matching(
        const WriterProxyData* wdata,
        const ReaderProxyData* rdata,
        MatchingFailureMask& reason,
        fastdds::dds::PolicyMask& incompatible_qos)
{
    reason.reset();
    incompatible_qos.reset();

    if (wdata->topic_name != rdata->topic_name)
    {
        reason.set(MatchingFailureMask::different_topic);
        return false;
    }

    if ((wdata->has_type_information() && wdata->type_information.assigned()) &&
            (rdata->has_type_information() && rdata->type_information.assigned()))
    {
        if (!is_same_type(wdata->type_information.type_information, rdata->type_information.type_information))
        {
            reason.set(MatchingFailureMask::different_typeinfo);
            return false;
        }
    }
    else
    {
        if (wdata->type_name != rdata->type_name)
        {
            reason.set(MatchingFailureMask::inconsistent_topic);
            return false;
        }
    }

    if (wdata->topic_kind != rdata->topic_kind)
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS:Remote Reader " << rdata->guid << " is publishing in topic "
                                                                         << rdata->topic_name << "(keyed:" << rdata->topic_kind <<
                "), local writer publishes as keyed: " << wdata->topic_kind);
        reason.set(MatchingFailureMask::inconsistent_topic);
        return false;
    }

    if (!rdata->is_alive()) //Matching
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "ReaderProxyData object is NOT alive");

        return false;
    }

    if ( wdata->reliability.kind == dds::BEST_EFFORT_RELIABILITY_QOS
            && rdata->reliability.kind == dds::RELIABLE_RELIABILITY_QOS)
    //Means our writer is BE but the reader wants RE
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS (topic: " << rdata->topic_name << "):Remote Reader "
                                                                   << rdata->guid <<
                " is Reliable and local writer is BE ");
        incompatible_qos.set(fastdds::dds::RELIABILITY_QOS_POLICY_ID);
    }

    if (wdata->durability.kind < rdata->durability.kind)
    {
        // TODO (MCC) Change log message
        EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS (topic: " << rdata->topic_name << "):RemoteReader "
                                                                   << rdata->guid <<
                " has TRANSIENT_LOCAL DURABILITY and we offer VOLATILE");
        incompatible_qos.set(fastdds::dds::DURABILITY_QOS_POLICY_ID);
    }

    if (wdata->ownership.kind != rdata->ownership.kind)
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS (topic: " << rdata->topic_name << "):Remote reader "
                                                                   << rdata->guid << " has different Ownership Kind");
        incompatible_qos.set(fastdds::dds::OWNERSHIP_QOS_POLICY_ID);
    }

    if (wdata->deadline.period > rdata->deadline.period)
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS (topic: " << rdata->topic_name << "):Remote reader "
                                                                   << rdata->guid << " has smaller DEADLINE period");
        incompatible_qos.set(fastdds::dds::DEADLINE_QOS_POLICY_ID);
    }

    if (!wdata->disable_positive_acks.enabled && rdata->disable_positive_acks.enabled)
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "Incompatible Disable Positive Acks QoS: writer is enabled but reader is not");
        incompatible_qos.set(fastdds::dds::DISABLEPOSITIVEACKS_QOS_POLICY_ID);
    }

    if (wdata->liveliness.lease_duration > rdata->liveliness.lease_duration)
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "Incompatible liveliness lease durations: offered lease duration "
                << wdata->liveliness.lease_duration << " must be <= requested lease duration "
                << rdata->liveliness.lease_duration);
        incompatible_qos.set(fastdds::dds::LIVELINESS_QOS_POLICY_ID);
    }

    if (wdata->liveliness.kind < rdata->liveliness.kind)
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "Incompatible liveliness kinds: offered kind is < requested kind");
        incompatible_qos.set(fastdds::dds::LIVELINESS_QOS_POLICY_ID);
    }

    // DataRepresentationQosPolicy
    if (!checkDataRepresentationQos(wdata, rdata))
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "Incompatible Data Representation QoS");
        incompatible_qos.set(fastdds::dds::DATAREPRESENTATION_QOS_POLICY_ID);
    }

#if HAVE_SECURITY
    // TODO: Check EndpointSecurityInfo
#endif // if HAVE_SECURITY

    //Partition mismatch does not trigger status change
    if (incompatible_qos.any())
    {
        reason.set(MatchingFailureMask::incompatible_qos);
        return false;
    }

    //Partition check:
    bool matched = false;
    if (wdata->partition.empty() && rdata->partition.empty())
    {
        matched = true;
    }
    else if (wdata->partition.empty() && rdata->partition.size() > 0)
    {
        for (auto rnameit = rdata->partition.begin();
                rnameit != rdata->partition.end(); ++rnameit)
        {
            if (StringMatching::matchString("", rnameit->name()))
            {
                matched = true;
                break;
            }
        }
    }
    else if (wdata->partition.size() > 0 && rdata->partition.empty())
    {
        for (auto wnameit = wdata->partition.begin();
                wnameit !=  wdata->partition.end(); ++wnameit)
        {
            if (StringMatching::matchString(wnameit->name(), ""))
            {
                matched = true;
                break;
            }
        }
    }
    else
    {
        for (auto wnameit = wdata->partition.begin();
                wnameit !=  wdata->partition.end(); ++wnameit)
        {
            for (auto rnameit = rdata->partition.begin();
                    rnameit != rdata->partition.end(); ++rnameit)
            {
                if (StringMatching::matchString(wnameit->name(), rnameit->name()))
                {
                    matched = true;
                    break;
                }
            }
            if (matched)
            {
                break;
            }
        }
    }
    if (!matched) //Different partitions
    {
        EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS (topic: " << rdata->topic_name << "): Different Partitions");
        reason.set(MatchingFailureMask::partitions);
    }

    return matched;
}

共进行十一项判断。

1topic name

topic name不相同,则不匹配。

2data type

要分两种情况讨论:

(1)包含TypeInfomation

(2)不包含TypeInfomation

当使用fastddsgen通过idl生成代码时,默认情况下生成xxxTypeObjectSupport.hpp和xxxTypeObjectSupport.cxx文件,生成这两个文件,则包含TypeInfomation;如果使用选项-no-typeobjectsupport,则不会生成上述两个文件,那么就不会包含TypeInfomation。

(1)包含TypeInfomation

①不要求struct所在的module完全相同

②如下两个data type,是可以相互通信的,虽然FinalDataType1和FinalDataType2,Pad1和Pad2,DataTypeUnion1和DataTypeUnion2名字都不一样,但是数据内容是相同的。

③如果数据成员的名字不一样,那么会匹配失败,比如数据成员data,一个是data1,一个是data2,那么不能匹配。

//data type 1

typedef uint8 BaseDataType[1024];

union DataTypeUnion1 switch (char){

case 1: BaseDataType DataTypeUnion_1;

};

struct Pad1 {

uint16 index;

string message;

};

typedef DataTypeUnion1 UnionAlias;

struct FinalDataType1 {

uint16 id;

UnionAlias data;

Pad1 pad;

};

//data type 2

module hello {

typedef uint8 BaseDataType[1024];

union DataTypeUnion2 switch (char){

case 1: BaseDataType DataTypeUnion_1;

};

struct Pad2 {

uint16 index;

string message;

};

typedef DataTypeUnion UnionAlias;

struct FinalDataType2 {

uint16 id;

UnionAlias data;

Pad2 pad;

};

};

(2)不包含TypeInfomation

在这种情况下,需要双方的data type的struct名相同,同时也要module相同,这里的module类似于命名空间。如下两个数据类型,均是struct HelloWorld,并且数据成员也完全相同,但是这两种数据类型无法匹配,因为两者的module不同。

①当struct name完全相同,并且module完全相同,则可以通信。

②数据成员的名字可以不一样。

//data type 1

struct HelloWorld {

uint16 index;

string message

}

//data type 2

module hello {

struct HelloWorld {

uint16 index;

string message

}

}

3topic kind

topic kind分WITH_KEY和NO_KEY这两种类型。决定于idl中定义的struct中,有没有数据成员用@Key进行修饰。topic kind和topic instance说的是一回事。

example中的topic instances这个例子,说明了key的使用。

topic实例,是通过key来实现的,比如定义摄像头数据,可以定义一个数据类型。但是摄像头有前、后、左、右四个摄像头,四个摄像头都使用相同的数据类型,那么怎么区分数据来自于哪个摄像头呢?如果让我们自己实现,我们在定义数据类型的时候,除了摄像头数据之外,我们还可以增加一个id属性,用id来区分数据来源于哪个摄像头,这种方式直观,好理解,完全可行。

那么这么简单,易实现的功能,fastdds为什么还要专门提供topic instance来实现这样的功能呢?

  • 与Qos策略的深度绑定

从fastdds官方文档上来看的话,有一些qos策略,比如HistoryQosPolicy、ResourceLimitWosPolicy,均可以对每个topic instance生效。

  • 实例句柄

从example中的topic instances例子来看,DataWriter在创建的时候,可以针对每个key创建一个句柄,这样在发送数据的时候,不需要对数据类型中的key字段进行赋值,只需要使用key对应的句柄进行发送即可。

创建句柄:

发送数据:

  • 状态机

fastdds对每个topic instance维护状态机。试想,如果我们自己实现对每个instance的状态机的维护,还是有一定的复杂度的。

ALIVE:实例存活,有DataWriter发数据

DISPOSED:当前writer不发送数据了

4ReliabilityQosPolicy

只要不是上图中的组合,那么就是匹配的。如下三种情况,均是匹配的:

|--------|--------|
| reader | writer |
| RE | RE |
| BE | BE |
| BE | RE |

5DurabilityQosPolicy

当加入一个微信群或者钉钉群的时候,能不能看到加入之前的聊天记录?

假如DataWriter先起来,并且已经写了一些数据,之后有新的DataReader起来,那么新起来的DataReader能不能接收到它启动之前,DataWriter发布的数据呢。DurabilityQosPolicy用来做这种控制。

VOLATILE_DURABILITY_QOS:易失的,新上线的DataReader只能读取上线之后,DataWriter发布的数据。

TRANSIENT_LOCAL_DURABILITY_QOS:会在内存中保存数据,新上线的DataReader也能读取上线之前的数据,前提是DataWriter还在,如果这个时候DataWriter已经退出了,那么DataReader就无法获取到。对于一些控制指令,一般情况下是不能丢的,往往使用这种qos。在ap autosar中,使用了中中qos做服务发现。

cpp 复制代码
/**
 * Enum DurabilityQosPolicyKind_t, different kinds of durability for DurabilityQosPolicy.
 */
typedef enum DurabilityQosPolicyKind : fastdds::rtps::octet
{
    /**
     * The Service does not need to keep any samples of data-instances on behalf of any DataReader that is not
     * known by the DataWriter at the time the instance is written. In other words the Service will only attempt
     * to provide the data to existing subscribers
     */
    VOLATILE_DURABILITY_QOS,
    /**
     * For TRANSIENT_LOCAL, the service is only required to keep the data in the memory of the DataWriter that
     * wrote the data and the data is not required to survive the DataWriter.
     */
    TRANSIENT_LOCAL_DURABILITY_QOS,
    /**
     * For TRANSIENT, the service is only required to keep the data in memory and not in permanent storage; but
     * the data is not tied to the lifecycle of the DataWriter and will, in general, survive it.
     */
    TRANSIENT_DURABILITY_QOS,
    /**
     * Data is kept on permanent storage, so that they can outlive a system session.
     *
     * @warning Not Supported
     */
    PERSISTENT_DURABILITY_QOS
} DurabilityQosPolicyKind_t;

hello world

仍然以fastdds自带的hello world做为例子进行测试。

默认情况下,DataWriter的qos类型为TRANSIENT_LOCAL_DURABILITY_QOS,DataReader为VOLATILE_DURABILITY_QOS。如果直接像下边这样进行修改,不生效的,因为DurabilityQosPolicy的使用要同时结合HistoryQosPolicy和ResourceLimitsQosPolicy以及ReliabilityQosPolicy进行统一配置。

  • ReliabilityQosPolicyKind需要配置可靠类型
  • HistoryQosPolicy配置KEEP_LAST_HISTORY_QOS,则depth需要大于0;或者配置为KEEP_ALL_HISTORY_QOS
  • ResourceLimitsQosPolicy中max_samples_per_instance不可小于HistoryQosPolicy中的depth

DataWriterQos writer_qos = DATAWRITER_QOS_DEFAULT;

publisher_->get_default_datawriter_qos(writer_qos);

auto max_samples = 500;

writer_qos.reliability().kind = ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS;

writer_qos.durability().kind = DurabilityQosPolicyKind::TRANSIENT_LOCAL_DURABILITY_QOS;

writer_qos.history().kind = HistoryQosPolicyKind::KEEP_LAST_HISTORY_QOS;

writer_qos.history().depth = max_samples;

writer_qos.resource_limits().max_samples_per_instance= max_samples;

writer_qos.resource_limits().max_samples = writer_qos.resource_limits().max_instances * max_samples;

writer_ = publisher_->create_datawriter(topic_, writer_qos, this, StatusMask::all());

6OwnershipQosPolicy

可以基于这两种qos策略实现选主主备切换的功能。可以基于此实现冗余设计,提升可靠性。

(1)选主

DataWriter1和DataWriter2的OwnershipQosPolicy都是独占类型,OwnershipStrengthQosPolicy的strength分别是100和90。那么两者同时发送数据的时候,DataReader只会读取DataWriter1的数据。DataWriter1被选为主。

(2)主备切换

当DataWriter1下线时,DataReader会读取DataWriter2的数据。DataWriter1下线之后,再上线,那么DataWriter1会再次成为主。

(3)两个DataWriter的strength相同

选择GUID较小的为主。

(4)实际数据收发

当DataWriter1和DataWriter2同时存在时,DataWriter2的数据会真正被发送,也会被DataReader所在的进程接收,但是对于用户是读取不到DataWriter2的数据的。

TODO:待验证,通过tcpdump抓包,在DataReader所在的机器,能抓到DataWriter2发送的数据。当DataWriter1和DataWriter2同时存在,DataReader和DataWriter2在不同的机器上。

7DeadlineQosPolicy

DeadlineQosPolicy应用在DataWriter、DataReader、Topic。该qos用来监督数据是不是按照预期的频率进行收发。假如数据是周期性发送和接收,周期是固定的100ms,我们如果想要监督数据收发是不是按照预期的周期进行的,那么就可以配置DeadlineQosPolicy。

以fastdds example中的hello_world为例,数据每100ms发送一次,我们配置DataWriter和DataReader的deadline为90ms,

同时在deadline missed回调函数中加打印,当有deadline missed发生时,函数被调用,并打印日志。运行代码,可以看到相关的日志打印。

DataWriter的deadline要小于等于DataReader的,否则无法匹配。

我们将DataWriter的deadline设置为100ms,将DataReader的deadline设置为90,这样两者无法匹配。通过函数EDP::valid_matching设置断点进行跟踪,结果如下:

qos的匹配规则,DataWriter和DataReader之间的匹配规则,往往需要DataWriter侧比DataReader更加严格。DataWriter需要能够包容DataReader的情况,比如针对ReliabilityQosPolicy,DataWriter是RELIABLE_RELIABILITY_QOS,DataReader是BEST_EFFORT_RELIABILITY_QOS,是匹配的;反过来不匹配。

DataWriter,写数据的一方是主动发送数据的一方;DataReader,收数据的一方是被动接收。主动的一方是基础,决定最终的表现。如果DataWriter不是可靠的,那么DataReader配置可靠,也是无法保证的。

8DisablePositiveACKsQosPolicy

9LivelinessQosPolicy

10DataRepresentationQosPolicy

11PartitionQosPolicy

(1)如果DataReader和DataWriter均没有设置PartitionQosPolicy,也就是两者的partiton均是空,那么匹配

(2)如果双方都设置了partition,那么进行两层遍历,如果有双方有partition相同,那么匹配;否则,不匹配

(3)如果其中一方没有配置partittion,以防配置了partition,那么按如下逻辑进行匹配,在配置了partition的一层,空字符串也是一个有效的partition name。也就是说如果一方没有配置partitition,以防配置了partittion,有两个partition name,分别是"hello"和"world",没有空字符串,那么双方是无法匹配的,只有配置了空字符串的partition,双方才可以匹配。

默认情况下,当同一个domain的DataWriter和DataReader的topic相同、qos策略相同、data type相同,那么两者可以相互发现;否则,不能相互发现。

PartitionQosPolicy可以对topic进行分区,使得DataWriter和DataReader在满足默认条件的情况下,还要满足PartitionQosPolicy的匹配规则,才能相互发现。

fastdds example中的cnfiguration可以用来测试PartitionQosPoliocy。

①PartitionQosPolicy作用于Subscriber和Publisher

②用一个string表示Subscriber和Publisher支持的一个Partition

③支持指定多个Partition

④支持空的字符串,也就是空的字符串也是有效的

一个DataWriter和DataReader如果要进行相互发现,进行通信,那么需要满足下面几点:

①在一个domain中,也就是domain id要相同

②topic相同

③data type匹配

④qos要匹配

⑤如果配置了partition,那么partition要兼容

publisher指定了三个Partition,一个hello,一个world,一个是空。

subscriber,没有指定Partition,可以与publisher的空Partition匹配,所以可以订阅到数据。

指定Partition为hello和world,也均能订阅到数据。

相关推荐
CSDN_RTKLIB14 小时前
CMake几个命令顺序
c++
weixin_4617694015 小时前
15. 三数之和
c++·算法·leetcode·三数之和
小镇学者16 小时前
【c++】C++字符串删除末尾字符的三种实现方法
java·开发语言·c++
ue星空16 小时前
R3注入反截图
c++
塔尖尖儿16 小时前
For循环中++i与i++有什么不一样?
c++
Ralph_Y17 小时前
C++虚继承
开发语言·c++
ZzZz_ing17 小时前
2026 - 零碎知识随记录
c++
SweetCode17 小时前
【无标题】
开发语言·c++·算法
王老师青少年编程17 小时前
信奥赛C++提高组csp-s之拓扑排序详解
c++·算法·拓扑排序·csp·信奥赛·csp-s·提高组