RTPS代码阅读记录

RTPS代码阅读记录

在这里插入图片描述

Stateful类型Writer的相关类结构:

PDP包的发送间隔是100毫秒(找到在哪里设置的)

A: void PDP::set_initial_announcement_interval()函数中设置
initial_announcements_.period = { 0, 1000000 };

如果订阅端希望上线后,可以收到发布端之前已经发布的消息,则发布和订阅端必须设置如下Qos

durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
reliability().kind = RELIABLE_RELIABILITY_QOS;
此外,发布端可以重发多少已经发布的消息,取决于其设置的history的depth
wqos.history().depth = 2; //history只存2个历史消息,因此最多能发两个订阅端上线前已经发布的消息
但是,如果wqos.history().kind如果设置了KEEP_ALL_HISTORY_QOS,那么发布端会无视上面设置的history().depth,将所有历史消息都补发给订阅端

很多子消息中有count元素,这个count元素的作用是什么?

A: 类似于SequenceNumber,用于非Data子消息,防止对端Reader读到重复数据

Reader依据什么条件判定对端Writer失效了?

A: a.第一种情况是对端的Participant都销毁了,然后本地的PDP发现对端Partipant的PDP报文发送间隔超过了leaseDuration,这个时候就认为对端Participant下线
此时就会进行remove_participant的操作,顺带移除了远端Participant下面所有匹配的endpoint

PDP::check_remote_participant_liveliness    ( // 定时执行检查 )
     PDP::remove_remote_participant
 	    PDPSimple::removeRemoteEndpoints
 	    	StatefulReader::matched_writer_remove 

b.第二种情况是对端的Writer被销毁了 (Participant还在)

  StatefulReader::processDataMsg 
      EDPSimplePUBListener::onNewCacheChangeAdded    (收到的Change的状态不是ALIVE,说明对端下线了)
          PDP::removeWriterProxyData
              EDP::unpairWriterProxy
                  EDP::unpairWriterProxy
                      StatefulReader::matched_writer_remove 

Writer依据什么条件判定对端Reader实效了?

A: 和Reader判定Writer实效的条件基本一致,就是PDP报文超时或者EDP报文中的EDPSImpleSUBListener收到非ALIVE状态的Change标识对端Reader下线了

ReaderQos的History的length设置的过短会不会导致丢失数据?

A: 使用HelloWorld测试程序设置订阅端DataReaderQos的history.length=1,通过publisher端按照1000hz的频率发送数据,测试10分钟,没有丢包

网卡白名单是在哪个配置中设置的?

A: 默认如果我们创建RTPSParticipant的时候,不指定transport,那么这个transport只有一个默认的UDPv4Transport可以使用,这个UDPv4Transport是没有网卡白名单的。
因此,我们首先要指定用户级别的Transport (不是builtin内建的),指定我们需要我们创建的Reader/Writer通过什么传输方式,并且设置走哪些网卡通信
这些设置是在TransportDescriptor上的,并且最终需要配置在Participant上面,
然后我们需要设置Participant的Qos,让RTPSParticipantImpl不要自己用builtin的Transport,而是使用用户创建的Transport
代码如下:

auto transportDescriptor = std::make_shared<UDPv4TransportDescriptor>();   	// 创建用户自己的UDPv4传输类型
transportDescriptor->interfaceWhiteList.emplace_back("192.168.7.22"); 	// 这里的参数可以传网卡名称  // 添加网卡白名单
DomainParticipantQos pqos;      						// ParticipantQos
pqos.transport().user_transports.push_back(transportDescriptor);  		// 将用户自己创建的udptransport添加到user_transports中
pqos.transport().use_builtin_transport = false;    				// 不使用builtin的Transport,使用用户设置在user_transports中的传输方式

ReaderProxy的作用?

A: 在StatefulWriter中代表对端匹配的某个Reader,保存了和该Reader的数据通信状态,主要是CacheChange的状态(发送过的Change的最大SN,对端Reader请求的Change的最大SN以及已经确认接收的Change的集合)
此外,ReaderProxy还提供了对相关Change状态的查询 (例如查询某个Change是否已经不在队列中了) 和更新接口 (状态在ChangeForReaderStatus_t枚举中定义)
当StatefulWriter向Reader发送数据以及处理该Reader收到的心跳包时,都会同步更新ReaderProxy中CacheChange记录的状态 (实际是一个结构体,其中包含了CacheChange的指针)

​ 在ReaderProxy中,CacheChange有如下几种状态:

enum ChangeForReaderStatus_t
{
    UNSENT = 0,                    //!< UNSENT           Writer未发送
    REQUESTED = 1,                 //!< REQUESTED        Reader请求发送
    UNACKNOWLEDGED = 2,            //!< UNACKNOWLEDGED   Reader未接收到
    ACKNOWLEDGED = 3,              //!< ACKNOWLEDGED     Reader已接收到
    UNDERWAY = 4                   //!< UNDERWAY         准备发送中
};

​ ReaderProxy保存了发给对端Reader的CacheChange的状态,Change指针以及序列号,保存在ChangeForReader_t结构体中

class ChangeForReader_t
{
    ChangeForReaderStatus_t status_;    	// 该CacheChange的状态
    SequenceNumber_t seq_num_;      	// CacheChange的序号
    CacheChange_t* change_;    		// 关联的CacheChange
    FragmentNumberSet_t unsent_fragments_;  
}

​ ReaderProxy的主要成员如下:

bool is_active_;  // 标记远端Reader是否还在线,stop了之后is_active_为false
ReaderLocator locator_info_;  // 对端Reader通信地址
DurabilityKind_t durability_kind_;  // 对端Reader的Durability属性(EDP报文中获取到)
bool expects_inline_qos_;  // 是否希望数据报文中携带Qos信息用于对端过滤
bool is_reliable_;  // 标记对端Reader的Reliability属性
bool disable_positive_acks_;  // 标记是否禁止对端Reader主动发送ACKNACK(一般在匹配上后,对端Reader会立刻发送一包ACKNACK子消息)
StatefulWriter* writer_;  // 和该ReaderProxy配对的本端Writer
ResourceLimitedVector<ChangeForReader_t, std::true_type> changes_for_reader_;  // 和该Reader关联的Change集合以及Change的状态
TimedEvent* nack_supression_event_;  // 当收到对端Reader的ACKNACK后,Writer并不是立刻进行补发,而是设置了定时器做补发的动作,因为这个时候还有新的Change在添加到History
				         // 直到添加新Change的动作暂时停止(就是间隔超过了nack_supression_event_设置的定时,nack_supression_event_定时器才启动进行补发)
				         // nack_supression_event_的定时是在创建StatefulWriter的时候通过WriterAttributes传入的(见WriterTimes结构体)
TimedEvent* initial_heartbeat_event_;  // 发送第一包心跳包的定时器(延时器)
std::atomic_bool timers_enabled_;  // 是否开启心跳包和ANCKANC回复定时器(依据是否有远程reliable类型的Reader和该Writer匹配来决定是否开启)
uint32_t last_acknack_count_;  // 最后一次收到的ACKNACK子消息中的count元素的值
uint32_t last_nackfrag_count_;  // 最后一次收到的NACKFrag子消息中的count元素的值
SequenceNumber_t changes_low_mark_;  // WriterHistory中CacheChange中序号最小值
bool active_ = false;  // 当WriterHistory中的Change被发送到ReaderProxy代表的对端Reader时,active_为true(StatefulWriter::deliver_sample_to_network)

​ 其中nack_supression_event_和initial_heartbeat_event_的延时配置在ReaderProxy所属的StatefulWriter中配置,具体是在WriterTimers结构中配置

struct WriterTimes
{
    //! Initial heartbeat delay. Default value ~11ms.
    Duration_t initialHeartbeatDelay;    // 初始心跳包的发送延时,默认11毫秒
    //! Periodic HB period, default value 3s. 
    Duration_t heartbeatPeriod;          // 默认正常心跳包发送间隔,默认3秒
    //!Delay to apply to the response of a ACKNACK message, default value ~5ms.
    Duration_t nackResponseDelay;	     // 响应acknack的回复的延时,默认 5毫秒
    //!This time allows the RTPSWriter to ignore nack messages too soon after the data as sent, default value 0s.
    Duration_t nackSupressionDuration;   // 如果收到的ACKNACK中有missing的change,那么需要延时后才补发,这个就是对应的延时
}

Writer写入的CacheChange是否是通过ReaderProxy发给对端Reader的?如果不是,CacheChange发给Reader的流程是怎样的?

A: 不通过ReaderProxy发送给对端,可以在StatefulWriter中查找flow_controller_关键字,发送数据都是通过某一类FlowController(SyncFlowController/AsyncFlowController/PureSyncFlowController)来完成的
需要发送的Change需要添加给FlowController,FlowController通过FlowControllerFactory创建并且返回单例,提供了add_new_sample, add_old_sample,remove_change用于添加和移除要发送的CacheChange。
在ReaderProxy中是找不到对FlowController的直接或者间接调用 (提供过StatefulWriter的函数间接向FlowController中添加CacheChange)

写入ReaderProxy中CacheChange的状态变化是怎样的?

A:

RTPS中是如何完成Writer对后匹配的Reader做数据补发动作的?

A: 在ReaderProxy响应ACK报文的地方 (当Writer收到的ACK报文中,ACK部分回复的sequencenumber是0,这种情况就是Reader 匹配后,将所有History中的Change添加到ReaderProxy的changes_for_reader_中等待重新发送),具体的代码逻辑在ReaderProxy::requested_changes_set的中

bool ReaderProxy::acked_changes_set(
        const SequenceNumber_t& seq_num) {
    if (seq_num > changes_low_mark_) {
        
        // 正常情况下收到HeartBeat后Reader回复的ACKNACK(Reader中收到的change的SN不会小于History中最小的SN)
        ...  // 将changes_for_reader_中小于seq_num的change记录移除,标识对端Reader已经确认收到这些change
    }
	else
    {
        future_low_mark = changes_low_mark_ + 1;
		// 如果Writer设置的Durability是持久化类型的(非VOLATILE易失类型的),才进行补发历史Change
        // seq_number=SequenceNumber_t,传入的seq就是0(SequenceNumber_t()的值,在ReaderProxy的start时会这么做
        if (seq_num == SequenceNumber_t() && durability_kind_ != DurabilityKind_t::VOLATILE)
        {
            SequenceNumber_t current_sequence = seq_num;
            ...  // 设置要补发的Change的SN的起点
            for (; current_sequence <= changes_low_mark_; ++current_sequence)
            {
            	// 将WriterHistory中的Change添加到changes_for_reader_中
                ChangeForReader_t cr(change);
                cr.setStatus(UNACKNOWLEDGED);
                changes_for_reader_.push_back(cr);
            }
        }
    }
}

void ReaderProxy::start(
        const ReaderProxyData& reader_attributes,
        bool is_datasharing)
{
    ...
    if (durability_kind_ == DurabilityKind_t::VOLATILE)
    {
        ...
    }
    else
    {
        acked_changes_set(SequenceNumber_t());  // Simulate initial acknack to set low mark
    }
}

ReaderProxy在nack_supression_event_的定时器中做了什么事情?

A: 主要是将要发送的Change的状态改为未应答的,等待下一次NACK过来后再一起处理发送

bool ReaderProxy::perform_nack_supression()
{
    return 0 != convert_status_on_all_changes(UNDERWAY, UNACKNOWLEDGED);  // 将changes_for_reader_中UNDERWAY状态的Change改为UNACKNOWLEDGED,就是下次响应NACK的时候要带上这些Change一起发送
}

WriterProxy的作用?

A: 在Reliability为RELIABLE的情况下,当匹配到远端Writer的时候,StatefulReader为该远端Writer创建WriterProxy,用于保存该Writer的通信状态
(是否在先,发送的最后Change的SN,通信地址,GUID,GuidPrefix,是否发送过心跳包等等信息)

WriterProxy的主要成员如下:

enum StateCode
{
  IDLE = 0, // WriterProxy没有执行关键操作(执行ACKNACK回复, 处理心跳包,执行stop等等)
  BUSY,     // WriterProxy正在执行关键操作
  STOPPED,  // WriterProxy已经停止,标识远端Writer无效
};  // 描述对端Writer状态
TimedEvent* heartbeat_response_;  // 用于响应HeartBeat子消息发送ACKNACK的定时器
TimedEvent* initial_acknack_;  // 初始化WriterProxy的时候用于发送第一包ACKNACK的定时器   (需要测试验证)
std::atomic<uint32_t> last_heartbeat_count_;   // 最后收到的HeartBeat子消息的序列号(Count)
std::atomic<bool> heartbeat_final_flag_;    // 标识收到的HeartBeat子消息的FinalFlag(设置了则不需要回,没设置则需要回复)
bool is_alive_;  // 标识对端Writer是否存活   (怎么判断的?)
pool_allocator_t changes_pool_;   // CacheChange池
foonathan::memory::set<SequenceNumber_t, pool_allocator_t> changes_received_;  // 保存收到的所有Data子消息的序列号
SequenceNumber_t changes_from_writer_low_mark_;  // 当前可以让Reader读取到内容的CacheChange的最高序列号(也就是收到了实际DATA子消息的),对应的CacheChange状态就是RECEIVED
              			   	     // 而changes_from_writer_low_mark+1对应的就是状态为missing的第一个CacheChange
SequenceNumber_t max_sequence_number_;  //  对端Writer可以获取的最大的CacheChange的序列号(可能有些还没有发Data,只发了HeartBeat)
SequenceNumber_t last_notified_;  //  最近一次通知DDS层DataReaderImpl读取的Changes中sequence的最大值
ResourceLimitedVector<GUID_t> guid_as_vector_;   // 保存对应的对端Writer的GUID
ResourceLimitedVector<GuidPrefix_t> guid_prefix_as_vector_;  // 保存对应的对端Writer的GUID Prefix
bool is_on_same_process_;  // 标识对端Writer是否和Reader在同一个进程中
uint32_t ownership_strength_;  // Qos中的Onwership Length属性,同一时刻当有多个Writer在Reader订阅的topic上发送数据时,ownership_strength_最高的那个Writer有高优先级被处理
   (The middleware will use the Ownership Strength to decide which DataWriter's data will be delivered to DataReaders. The DataWriter with the highest Ownership Strength value will be the owner of the instances and whose data is delivered to DataReaders. )  // https://community.rti.com/kb/summary-and-typical-use-cases-different-quality-service-qos-parameters
GUID_t persistence_guid_;  // PERSISTENCE服务的GUID(用于对Writer/Reader的History做持久化)
LocatorSelectorEntry locators_entry_;  // 对端Writer的Locator信息的入口
bool is_datasharing_writer_;  // 标识对端Writer是否时通过数据共享方式发送Data的
bool received_at_least_one_heartbeat_;  // 标识对端Writer是否发送过HeartBeat子消息
std::atomic<StateCode> state_;  // 对端Writer的状态

什么时候会发送DataFrag子消息(而不是发送Data子消息)?

A: Data子消息有最大大小,当超过这个大小后,消息就会被分片,按照多个DataFrag子消息进行发送:

DeliveryRetCode StatefulWriter::deliver_sample_to_network(
        CacheChange_t* change,    // 要发送的Change
        RTPSMessageGroup& group,  // 组成要发送的RTPSMessage,需要依赖RTPSMessageGroup
        ...)
{
    ...
    uint32_t n_fragments = change->getFragmentCount();   // 分片数量
    ...
    if (0 < n_fragments)
    {
        ...
        if (group.add_data_frag(*change, min_unsent_fragment, inline_qos))   // 添加data_frag子消息
        {
            ...
        }
    }
    else
    {
        if (group.add_data(*change, inline_qos))    // 添加data子消息
        {
            ...
        }
    }
}

RTPSParticipantImpl选择哪种Flowcontroller的依据?

A: 首先,FlowController是用于控制发送CacheChange的,由FlowControllerFactory工厂类预先创建了几个builtin的FlowController:

void FlowControllerFactory::init(
        fastrtps::rtps::RTPSParticipantImpl* participant)
{
    participant_ = participant;
    // Create default flow controllers.

    // PureSyncFlowController -> used by volatile besteffort writers.
    flow_controllers_.insert(decltype(flow_controllers_)::value_type(
                pure_sync_flow_controller_name,
                std::unique_ptr<FlowController>(
                    new FlowControllerImpl<FlowControllerPureSyncPublishMode,
                    FlowControllerFifoSchedule>(participant_, nullptr))));
    // SyncFlowController -> used by rest of besteffort writers.
    flow_controllers_.insert(decltype(flow_controllers_)::value_type(
                sync_flow_controller_name,
                std::unique_ptr<FlowController>(
                    new FlowControllerImpl<FlowControllerSyncPublishMode,
                    FlowControllerFifoSchedule>(participant_, nullptr))));
    // AsyncFlowController
    flow_controllers_.insert(decltype(flow_controllers_)::value_type(
                async_flow_controller_name,
                std::unique_ptr<FlowController>(
                    new FlowControllerImpl<FlowControllerAsyncPublishMode,
                    FlowControllerFifoSchedule>(participant_, nullptr))));

#ifdef FASTDDS_STATISTICS
    flow_controllers_.insert(decltype(flow_controllers_)::value_type(
                async_statistics_flow_controller_name,
                std::unique_ptr<FlowController>(
                    new FlowControllerImpl<FlowControllerAsyncPublishMode,
                    FlowControllerFifoSchedule>(participant_, nullptr))));
#endif // ifndef FASTDDS_STATISTICS
}

​ 当创建Writer时,RTPSParticipantImpl会从FlowControllerFactory中找到合适的FlowController分配给新创建的RTPSWriter:

bool RTPSParticipantImpl::create_writer(...)
{
	...
	RTPSWriter* SWriter = nullptr;
    SWriter = callback(guid, param, flow_controller, persistence, param.endpoint.reliabilityKind == RELIABLE);
    ...
}

​ 下面看下在创建RTPSWriter的时候,具体是哪些参数/条件影响了RTPSParticipant分配给这个Writer的FlowController的:

## RTPSParticipantImpl.cpp
bool RTPSParticipantImpl::create_writer(
        RTPSWriter** writer_out,
        WriterAttributes& param,
        const EntityId_t& entity_id,
        bool is_builtin,
        const Functor& callback)
{
    ...
    GUID_t guid(m_guid.guidPrefix, entId);
    fastdds::rtps::FlowController* flow_controller = nullptr;
    const char* flow_controller_name = param.flow_controller_name;  // = FASTDDS_FLOW_CONTROLLER_DEFAULT
    // Support of old flow controller style.
    if (param.throughputController.bytesPerPeriod != UINT32_MAX && param.throughputController.periodMillisecs != 0)
    {   // 不满足,因为这个是老版本的属性,新版本中这个bytesPerPeriod和periodMillisecs都是默认只,不满足条件中的值(UINT32_MAX和0)
        ...
    }
    if (m_att.throughputController.bytesPerPeriod != UINT32_MAX && m_att.throughputController.periodMillisecs != 0)
    {
        // 不满足,原因同上
        ...
    }
    if (nullptr == flow_controller &&
            (fastdds::rtps::FASTDDS_FLOW_CONTROLLER_DEFAULT == flow_controller_name ||
            ASYNCHRONOUS_WRITER == param.mode))
    {   // 满足条件
        flow_controller = flow_controller_factory_.retrieve_flow_controller(flow_controller_name, param);
    }
    ...
}

## FlowControllerFactory.cpp
const char* const async_flow_controller_name = "AsyncFlowController";

FlowController* FlowControllerFactory::retrieve_flow_controller(
        const std::string& flow_controller_name,
        const fastrtps::rtps::WriterAttributes& writer_attributes)
{
    FlowController* returned_flow = nullptr;

    // Detect it has to be returned a default flow_controller.
    if (0 == flow_controller_name.compare(FASTDDS_FLOW_CONTROLLER_DEFAULT))
    {
        if (fastrtps::rtps::SYNCHRONOUS_WRITER == writer_attributes.mode)
        {
            ...
        }
        else
        {
            returned_flow = flow_controllers_[async_flow_controller_name].get();  //使用AsyncFlowController
        }
    }
    ...
}

​ 从上面的逻辑可以看到是writerAttribute中的flow_controller_name以及mode两个参数决定了最后从FlowControllerFactory中返回哪个FlowController,RTPSParticipantImpl构造RTPSWriter时传入的WriterAttribute中的mode以及flow_controller_name的参数来源如下:

ReturnCode_t DataWriterImpl::enable()
{
    // DataWriterQos qos_;  // DataWriterImpl.h中定义
	...
    // mode和flow_controller_name收到writerqos中的publish_mode参数的影响
    w_att.mode = qos_.publish_mode().kind == SYNCHRONOUS_PUBLISH_MODE ? SYNCHRONOUS_WRITER : ASYNCHRONOUS_WRITER;
    w_att.flow_controller_name = qos_.publish_mode().flow_controller_name;
    ...
}

void RtpsTransmitter<M>::Enable() {
    ...
    eprosima::fastdds::dds::DataWriterQos wqos = eprosima::fastdds::dds::DATAWRITER_QOS_DEFAULT;
    ...
    wqos.publish_mode().kind = eprosima::fastdds::dds::PublishModeQosPolicyKind::ASYNCHRONOUS_PUBLISH_MODE;  //使用ASYNC模式
    ...
    writer_ = publisher_->create_datawriter(topic, wqos);
    ...
}

class DataWriterQos
{
public:
    ...
private:
    ...
    //!Publication Mode Qos, implemented in the library.
    PublishModeQosPolicy publish_mode_;
    ...
}

class PublishModeQosPolicy : public QosPolicy
{
public:
    PublishModeQosPolicyKind kind = SYNCHRONOUS_PUBLISH_MODE;
    const char* flow_controller_name = fastdds::rtps::FASTDDS_FLOW_CONTROLLER_DEFAULT;   // 默认flow_controller_name(DEFAULT)
    ...
}

采用静态发现模式后,发现wireshark抓包,DATA数据包无法显示topic名称,是什么原因?

A: 因为wireshare解析Data数据包时,是根据writer的entityid去之前找到的EDP数据包中查找该entity是关联在哪个topic上的
在这里插入图片描述
而使用静态发现后,只有PDP数据包,没有EDP数据包,因此wireshark就无法根据entityid直到这个writer在哪个topic上通信了,因此就无法显示topic名称了。

用jni封装FastDDS的接口(白名单设置也最好加到接口中,读取数据接口包括主动和被动接收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值