前言
本章介绍Subscriber。
一、Subscriber
订阅是通过DataReader与Subscriber实体来实现的。DataReader由Subscriber创建,DataReader将绑定到将要接收的数据类型的Topic。然后,DataReader将开始接收来自与此主题匹配的远程Publisher发布的消息。
当Subscriber接收到数据时,它通知用户应用程序有新数据可用,应用程序可以使用DataReader来获取接收到的数据。
Subscriber
Subscriber作为一个容器,可创建多个DataReader,通过SubscriberQos对其进行管理。各个DataReader会SubscriberQos的统一影响,除此之外是相互独立的。Subscriber可以为不同的主题数据托管不同的DataReader。
- SubscriberQos类包含了适用于Subscriber的策略:
QosPolicy class | Accessor/Mutator |
---|---|
PresentationQosPolicy | presentation() |
PartitionQosPolicy | partition() |
GroupDataQosPolicy | group_data() |
EntityFactoryQosPolicy | entity_factory() |
-
SubscriberListener监听器类:
SubscriberListener继承于DataReaderListener,默认情况下会优先调用DataReaderListener的回调,除非DataReaderListener没有匹配的回调或者被StatusMask屏蔽了相应状态,此类情况下会调用SubscriberListener的回调。
SubscriberListener另外新增了on_data_on_reader的回调:这个回调是对所有包含的DataReader有效,只有任意一个DataReader有数据达到,则触发回调。当同时有几个DataReader有数据到达时,只会触发一次回调,必须在回调函数中把所有DataReader数据读完。 -
创建Subscriber
由DomainParticipant调用create_subscriber创建,必须传入参数SubscriberQos,可选参数SubscriberListener和StatusMask。
二、DataReader
DataReader由Subscriber创建,必须与一个Topic绑定。Topic必须在DataReader创建前存在。DataReader创建之后,在接受到Publisher的发布后,应用程序可以调用DataReader的DataReader::read_next_sample()或DataReader::take_next_sample()成员函数获取数据。
- DataReaderQos类包含了适用于DataReader的Qos策略:
QosPolicy class | Accessor/Mutator |
---|---|
DurabilityQosPolicy | durability() |
DurabilityServiceQosPolicy | durability_service() |
DeadlineQosPolicy | deadline() |
LatencyBudgetQosPolicy | latency_budget() |
LivelinessQosPolicy | latency_budget() |
ReliabilityQosPolicy | reliability() |
DestinationOrderQosPolicy | destination_order() |
HistoryQosPolicy | history() |
ResourceLimitsQosPolicy | resource_limits() |
LifespanQosPolicy | lifespan() |
UserDataQosPolicy | user_data() |
OwnershipQosPolicy | ownership() |
PropertyPolicyQos | properties() |
RTPSEndpointQos | endpoint() |
ReaderResourceLimitsQos | reader_resource_limits() |
RTPSEndpointTimeBasedFilterQosPolicy | time_based_filter() |
ReaderDataLifecycleQosPolicy | reader_data_lifecycle() |
RTPSReliableReaderQos | reliable_reader_qos() |
TypeConsistencyQos | type_consistency() |
DataSharingQosPolicy | data_sharing() |
-
DataReaderListener监听器类,包含以下回调函数:
on_data_available():在DataReader上有应用程序可用的新数据更新时,触发回调。如果一次接收到几个新的数据更改,则只能为所有这些更改发出一个回调调用,而不是每个更改发出一个回调调用。如果应用程序在此回调中检索接收到的数据,则必须继续读取数据,直到没有留下新的更改。
on_subscription_matched():DataReader匹配到DataWriter,或者与之前已匹配的DataWriter停止连接,会触发回调。
on_requestd_deadline_missed ():DataReader没有在DataReaderQos上配置的截止时间内接收数据。
on_requested_incompatible_qos(): DataReader已经找到了一个与Topic匹配的DataWriter,并且具有公共分区,但是QoS与DataReader上定义的QoS不兼容。
on_liveliness_changed():匹配的DataWriter的活跃状态发生了变化。非活动的DataWriter变成活动的,或者反过来。
on_sample_rejected():DataReader拒绝接受数据样本时,触发回调。拒绝的原因参考SampleRejectedStatusKind。
on_sample_lost():数据样本丢失时触发回调。 -
创建DataReader:
由Subscriber调用create_datareader,创建时必须绑定topic和DataReaderQos,DataReaderListener和StatusMask可选。
SampleInfo
DataReader返回读取到的数据时,同时会返回一个SampleInfo实例。SampleInfo包含了数据的其他附加信息。比如valid_data字段为false,则DataReader不会通知应用程序更新实例中的新值,而是只通知状态更改,并且必须丢弃返回的数据值。SampleInfo包含以下字段:
sample_state
表示之前是否已经读取了相应的数据样本。可以取以下值之一:
- READ:这是第一次检索该数据样本
- NOT_READ:数据样本之前已经被读取或获取过
view_state
表示这是否是DataReader检索的这个数据实例的第一个样本。它可以取以下值之一:
- NEW:这是第一次检索这个实例的样本
- NOT_NEW:已被检索过
instance_state
表示instance是还存在还是已被销毁。在后一种情况下,它还提供有关处置原因的信息。它可以取以下值之一:
- ALIVE:instance当前存在
- not_alive_handled:远程DataWriter处理了这个instance
- NOT_ALIVE_NO_WRITERS:由于发布该实例的远程DataWriter不在活动状态,DataReader已经处理了该instance
disposed_generation_count
指示实例在被处置后变为活动状态的次数
no_writers_generation_count
表示该instance被处理为NOT_ALIVE_NO_WRITERS状态后,变成活跃状态的次数
sample_rank
表示本次回调里面,该instance总共有多少次数据样本更新。比如为5,表示该DataReader有5个数据更新。
当前Fast DDS v2.14.1不支持。
generation_rank
表示在处理instance期间,又有新的更新到该instance的次数。
当前Fast DDS v2.14.1不支持。
absolute_generation_rank
当前Fast DDS v2.14.1不支持。
source_timestamp
DataWriter发布该样本数据时打上的时间戳。
instance_handle
本地instance的handle
publication_handle
发布该数据的DataWriter的句柄
valid_data
表示样本中的数据是否有效。
sample_identity
Sample_identity是请求者-应答者配置的扩展。它包含DataWriter和当前消息的序列号,应答器在发送应答时使用它来填充related_sample_identity。
related_sample_identity
Related_sample_identity是请求者-应答者配置的扩展。在应答消息中,它包含相关请求消息的sample_identity。请求者使用它将每个应答链接到适当的请求。
如何接受并处理DataReader的数据
可以通过调用DataReader的read_xxx和take_xxx接口。
Read_xxx接口:
- DataReader::read_next_sample:读取DataReader上可用的下一个未先前访问过的数据值,并将其存储在提供的数据缓冲区中
- DataReader::read()、DataReader::read_instance()和DataReader::read_next_instance()提供了获取符合特定条件的样本集合的机制。(应该是一次读取多个数据)
Take_xxx接口:
- DataReader::take_next_sample:读取DataReader上可用的下一个未先前访问过的数据值,并将其存储在提供的数据缓冲区中
- DataReader::take()、DataReader::take_instance()和DataReader::take_next_instance()提供了获取符合特定条件的样本集合的机制。(应该是一次读取多个数据)
read和take的区别在于,调用take类型的接口后,数据样本将从DataReader中移除,无法再访问。
调用read和take读取接口时,需关注数据序列的问题,可以自定义序列作为read和take接口的参数传入,也可以传入空序列由DDS分配,但必须调用return_loan释放。
调用read和take接口后,数据被缓存在序列中,可以调用序列类的length接口,获取总共有多个数据待处理。同时通过sampleinfo判断数据是否有效,再去取数据并处理。
官方示例代码:
// Sequences are automatically initialized to be empty (maximum == 0)
FooSeq data_seq;
SampleInfoSeq info_seq;
// with empty sequences, a take() or read() will return loaned
// sequence elements
ReturnCode_t ret_code = data_reader->take(data_seq, info_seq,
LENGTH_UNLIMITED, ANY_SAMPLE_STATE,
ANY_VIEW_STATE, ANY_INSTANCE_STATE);
// process the returned data
if (ret_code == ReturnCode_t::RETCODE_OK)
{
// Both info_seq.length() and data_seq.length() will have the number of samples returned
for (FooSeq::size_type n = 0; n < info_seq.length(); ++n)
{
// Only samples with valid data should be accessed
if (info_seq[n].valid_data && data_reader->is_sample_valid(&data_seq[n], &info_seq[n]))
{
// Do something with data_seq[n]
}
}
// must return the loaned sequences when done processing
data_reader->return_loan(data_seq, info_seq);
}
调用read和take的接口是基本的读取操作,fast DDS的技术手册介绍了几种数据读取及处理的机制
方式1:通过回调的方式读取
当DataReader从任何匹配的DataWriter接收到新的数据值时,它通过两个Listener回调通知应用程序:on_data_available和on_data_on_readers,官方示例代码:
class CustomizedDataReaderListener : public DataReaderListener
{
public:
CustomizedDataReaderListener()
: DataReaderListener()
{
}
virtual ~CustomizedDataReaderListener()
{
}
void on_data_available(
DataReader* reader) override
{
// Create a data and SampleInfo instance
Foo data;
SampleInfo info;
// Keep taking data until there is nothing to take
while (reader->take_next_sample(&data, &info) == ReturnCode_t::RETCODE_OK)
{
if (info.valid_data)
{
// Do something with the data
std::cout << "Received new data value for topic "
<< reader->get_topicdescription()->get_name()
<< std::endl;
}
else
{
std::cout << "Remote writer for topic "
<< reader->get_topicdescription()->get_name()
<< " is dead" << std::endl;
}
}
}
};
注意多个数据同时更新时,只会触发一次回调,因此在回调中,需循环调用take和read接口保证数据已读取完。
方式2:通过wait-set和condition机制+自建线程实现数据接收
通过Fast DDS提供的wait-set和condition机制,自建线程实现消息接收。
// Create a DataReader
DataReader* data_reader =
subscriber->create_datareader(topic, DATAREADER_QOS_DEFAULT);
if (nullptr == data_reader)
{
// Error
return;
}
// Prepare a wait-set to wait for data on the DataReader
WaitSet wait_set;
StatusCondition& condition = data_reader->get_statuscondition();
condition.set_enabled_statuses(StatusMask::data_available());
wait_set.attach_condition(condition);
// Create a data and SampleInfo instance
Foo data;
SampleInfo info;
//Define a timeout of 5 seconds
eprosima::fastrtps::Duration_t timeout (5, 0);
// Loop reading data as it arrives
// This will make the current thread to be dedicated exclusively to
// waiting and reading data until the remote DataWriter dies
while (true)
{
ConditionSeq active_conditions;
if (ReturnCode_t::RETCODE_OK == wait_set.wait(active_conditions, timeout))
{
while (ReturnCode_t::RETCODE_OK == data_reader->take_next_sample(&data, &info))
{
if (info.valid_data)
{
// Do something with the data
std::cout << "Received new data value for topic "
<< topic->get_name()
<< std::endl;
}
else
{
// If the remote writer is not alive, we exit the reading loop
std::cout << "Remote writer for topic "
<< topic->get_name()
<< " is dead" << std::endl;
break;
}
}
}
else
{
std::cout << "No data this time" << std::endl;
}
}
方式3:通过wait_for_unread_message阻塞接口+自建线程实现数据接收
使用DataReader::wait_for_unread_message()成员函数也可以达到同样的效果,该函数会阻塞,直到有新的数据样本可用或给定的超时过期。如果超时后没有新数据可用,则返回值为false。这个返回值为true的函数意味着DataReader上有新的可用数据可供应用程序检索。
// Create a DataReader
DataReader* data_reader =
subscriber->create_datareader(topic, DATAREADER_QOS_DEFAULT);
if (nullptr == data_reader)
{
// Error
return;
}
// Create a data and SampleInfo instance
Foo data;
SampleInfo info;
//Define a timeout of 5 seconds
eprosima::fastrtps::Duration_t timeout (5, 0);
// Loop reading data as it arrives
// This will make the current thread to be dedicated exclusively to
// waiting and reading data until the remote DataWriter dies
while (true)
{
if (data_reader->wait_for_unread_message(timeout))
{
if (ReturnCode_t::RETCODE_OK == data_reader->take_next_sample(&data, &info))
{
if (info.valid_data)
{
// Do something with the data
std::cout << "Received new data value for topic "
<< topic->get_name()
<< std::endl;
}
else
{
// If the remote writer is not alive, we exit the reading loop
std::cout << "Remote writer for topic "
<< topic->get_name()
<< " is dead" << std::endl;
break;
}
}
}
else
{
std::cout << "No data this time" << std::endl;
}
}