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,也均能订阅到数据。

