DDS(Data Distribution Service) 数据分发服务-05 Fast DDS框架-3(Subscriber)



前言

本章介绍Subscriber。


一、Subscriber

订阅是通过DataReader与Subscriber实体来实现的。DataReader由Subscriber创建,DataReader将绑定到将要接收的数据类型的Topic。然后,DataReader将开始接收来自与此主题匹配的远程Publisher发布的消息。
当Subscriber接收到数据时,它通知用户应用程序有新数据可用,应用程序可以使用DataReader来获取接收到的数据。
在这里插入图片描述

Subscriber

Subscriber作为一个容器,可创建多个DataReader,通过SubscriberQos对其进行管理。各个DataReader会SubscriberQos的统一影响,除此之外是相互独立的。Subscriber可以为不同的主题数据托管不同的DataReader。

  • SubscriberQos类包含了适用于Subscriber的策略:
QosPolicy classAccessor/Mutator
PresentationQosPolicypresentation()
PartitionQosPolicypartition()
GroupDataQosPolicygroup_data()
EntityFactoryQosPolicyentity_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 classAccessor/Mutator
DurabilityQosPolicydurability()
DurabilityServiceQosPolicydurability_service()
DeadlineQosPolicydeadline()
LatencyBudgetQosPolicylatency_budget()
LivelinessQosPolicylatency_budget()
ReliabilityQosPolicyreliability()
DestinationOrderQosPolicydestination_order()
HistoryQosPolicyhistory()
ResourceLimitsQosPolicyresource_limits()
LifespanQosPolicylifespan()
UserDataQosPolicyuser_data()
OwnershipQosPolicyownership()
PropertyPolicyQosproperties()
RTPSEndpointQosendpoint()
ReaderResourceLimitsQosreader_resource_limits()
RTPSEndpointTimeBasedFilterQosPolicytime_based_filter()
ReaderDataLifecycleQosPolicyreader_data_lifecycle()
RTPSReliableReaderQosreliable_reader_qos()
TypeConsistencyQostype_consistency()
DataSharingQosPolicydata_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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值