FastDDS服务发现之PDP和EDP的创建

Fast DDS提供了可以跨DomainParticipants主动发现和匹配DataWriters和DataReaders来共享数据的机制。本文简要介绍Fast DDS中不同的服务发现机制,重点分析Simple Discovery的对象创建

内置协议

BuiltinProtocols类是内置协议的主要处理器。BuiltinProtocols包括以下主要数据:

//协议属性字段
BuiltinAttributes m_att;
//服务发现的PDP对象
PDP* mp_PDP;
//WriterLiveness 对象,用于心跳保活
WLP* mp_WLP;
//!Pointer to the TypeLookupManager
fastdds::dds::builtin::TypeLookupManager* tlm_;
//!Locator list for metatraffic
LocatorList_t m_metatrafficMulticastLocatorList;
//!Locator List for metatraffic unicast
LocatorList_t m_metatrafficUnicastLocatorList;
//! Initial peers
LocatorList_t m_initialPeersList;
//! Known discovery and backup server container
std::list<eprosima::fastdds::rtps::RemoteServerAttributes> m_DiscoveryServers;

BuiltinAttributes类用于配置BuiltinProtocols所需要的内容,主要有以下字段:

DiscoverySettings discovery_config;

//是否使用保活协议(WriterLiveliness protocol)
bool use_WriterLivelinessProtocol = true;

//! TypeLookup Service settings
TypeLookupSettings typelookup_config;

//单播地址,这个默认是7410,0.0.0.0,它的作用是什么?
LocatorList_t metatrafficUnicastLocatorList;

//组播地址,默认为7400,239.255.0.1
LocatorList_t metatrafficMulticastLocatorList;

//! The collection of external locators to use for communication on metatraffic topics.
fastdds::rtps::ExternalLocators metatraffic_external_unicast_locators;

//! Initial peers.
LocatorList_t initialPeersList;

//! Memory policy for builtin readers
MemoryManagementPolicy_t readerHistoryMemoryPolicy =
MemoryManagementPolicy_t::PREALLOCATED_WITH_REALLOC_MEMORY_MODE;

//! Maximum payload size for builtin readers
uint32_t readerPayloadSize = BUILTIN_DATA_MAX_SIZE;

//! Memory policy for builtin writers
MemoryManagementPolicy_t writerHistoryMemoryPolicy =
MemoryManagementPolicy_t::PREALLOCATED_WITH_REALLOC_MEMORY_MODE;

//! Maximum payload size for builtin writers
uint32_t writerPayloadSize = BUILTIN_DATA_MAX_SIZE;

//! Mutation tries if the port is being used.
uint32_t mutation_tries = 100u;

BuiltinAttributes对象在什么地方被初始化?

这些内置协议包括:

  1. 参与者发现协议(Participant Discovery Protocol):该协议让参与者(即DDS应用)可以发现网络中的其他参与者。
  2. 发布者/订阅者发现协议(Publisher/Subscriber Discovery Protocol):这允许参与者发现网络中其他参与者的发布者和订阅者。
  3. 读写器发现协议(Reader/Writer Discovery Protocol):这使参与者可以发现网络中其他参与者的数据读写器。
  4. WLP保活协议(心跳,WriterLiveliness),主要由类WLP来实现

这些内置协议在Fast DDS中是自动启动并运行的,而BuiltinProtocols类就是用来管理和处理这些内置协议的。总的来说,BuiltinProtocols类的主要作用是管理和处理Fast DDS的内置协议,这些协议使得DDS应用可以在网络中发现和与其他应用交互。

发现机制

Simple Discovery

优点

  1. 简单易用,只要在同一网络中的节点启动Fast DDS应用,他就能自动发现彼此并建立连接
  2. 自动化,无需手动配置每个节点的网络地址,减轻了配置和维护的工作量
  3. 实时性,可以快速探测和连接节点,适合实时性要求高的系统

缺点

  1. 缺乏灵活性,不支持定制化的发现策略,可能不适合某些特殊需求
  2. 可扩展性问题:在大型复杂的系统中,需要所有节点之间进行广播,当节点数量非常多时可能会导致网络拥塞,所以缺乏可扩展性
  3. 安全性问题:Simple Discovery并未考虑到安全性问题,可能会导致节点攻击

适用场景

Static Discovery

Static Discovery使用SPDP(即上面的Simple Participant Discovery Protocol)作为PDP阶段,但跳过了SEDP,所有参与的节点在启动前必须已知。这些信息定义在XML文件中,即EDP阶段通过静态配置来代替SEDP协议。这在处理有限带宽和已知的DataWrites和DataReaders方面非常有用。如果所有的DataWriters和DataReaders,它们的Topics,data types都是事先已知的,EDP阶段可以用对端的静态配置替换。因此也只有在对端配置的部分才可以通信。

优点

  1. 安全。与Simple Discovery相比,Static Discovery可以更好的控制哪些节点可以连接到系统,从而提高系统的安全性
  2. 可扩展。不依赖于节点之间的广播,所以有较好的扩展性
  3. 灵活性。可以控制哪些节点应该连接在一起,使用更加灵活

缺点

  1. 需要手动配置每个节点的网络地址,增加了配置和维护的工作量;且如果拓扑结构发生变化,也需要手动修改
  2. 启动阶段,因为在系统启动阶段需要保证配置完成,否则可能导致启动失败或部分功能无法使用,导致启动较慢

适用场景

Discovery Server

这种发现机制使用中心化的策略,基于client-server模式。这种服务模式中,一个或多个DomainParticipants作为server,所有的其他节点作为client,向server注册信息,并从server获取其他节点的信息。
Discovery Server DomainParticipants可以是clients或者servers。分为四种模式:SERVERCLIENTBACKUPSUPPER CLIENT。它们之间的区别主要是节点发现过程中的角色和职责上:

  1. SERVER:SERVER是Discovery Server模式中的中心节点,它负责管理和存储所有节点(包括CLIENT,BACKUP,SUPER CLIENT)的元数据信息。当一个CLIENT节点需要发现其他节点信息时,它会像SERVER请求所需的元数据信息。具体来说,一个SERVER会发布clients和servers的信息,同时一个server可能会连接到另一个serser上来接收它的clients信息。
  2. CLIENT:CLIENT是需要与其他节点进行交互的节点。在Discovery Server模式中,CLIENT节点会连接到一个或多个SERVER,将自己的元数据信息注册到SERVER,并从SERVER获取它需要的,其他节点的元数据信息,以便与其他节点建立连接和进行通信。Clients需要预先知道它要连接的Servers。
  3. BAKCUP:备份是一种特殊的SERVER,它会将发现的信息写入持久化的数据库,用于Server发生故障时提供备份和恢复服务。BACKUP节点会定期从SERVER同步所有节点的元数据信息。如果主SERVER发生故障,CLIENT节点可以从BACKUP节点获取所需要的元数据信息,以便于继续与其他节点通信。
  4. SUPER CLIENT:特殊的CLIENT,可以直接与SERVER进行通信,而不需要通过BACKUP节点。这使得SUPER CLIENT可以在SERVER发生故障时直接从BACKUP节点获取元数据信息,而不需要等待BACKUP节点的同步。这可以提供系统的响应速度和可靠性。

优点

  1. 可扩展:对于大型系统,在Simple Discovery中每个节点都要互相通信,节点数量很大的情况下会造成大量通信从而导致网络拥塞,而在Discovery Server中节点之间的通信减少,大大减少了网络流量和节点处理负载
  2. 效率:点对点发现模式中,节点发现过程的时间通常会随着节点数量的增加而增加,而在Discovery Server模式中,由于所有的元素据都在服务器上,因此节点发现过程的时间基本上上固定的,不会随着节点的增加而增加。
  3. 灵活:Discovery Server提供一种更灵活的方式来管理拓扑结构,例如,节点可以部署到不同的网络子网中,只要可以访问到服务器即可,服务器可以根据需要来控制哪些客户端

缺点

  1. 服务器单点故障:如果发生故障,会导致所有节点无法发现和通信。虽然可以通过备份服务器来提高系统的可靠性,但会增加系统的复杂性和成本。
  2. 依赖服务器:所有节点都依赖服务器进行发现和通信,如果性能不足或者网络连接不稳定,整个系统的性能和稳定性都会受到影响
  3. 配置复杂:相比点到点的发现,需要配置服务器地址,端口,以及节点的角色和权限等信息。

适用场景

适用于大型和复杂的分布式系统

Manual Discovery

这种机制只与RTPS层兼容。没有PDP阶段,因此也就没有EDP阶段,所有匹配只能通过RTPS层的addReaderLocator, addReaderProxy, addWriterProxy方法

发现阶段

服务发现的创建和发现都是在BuiltinProtocols类中处理。所有的服务发现都可以分为两个阶段:

  1. Participant Discovery Phase(PDP):这个阶段DomainParticipant通过周期性的广播消息获取互相的存在。
  2. Endpoint Discovery Phase (EDP):这个阶段DataReader和DataWriter进行发现和匹配,对于两个要匹配的端点而言,topic和data type必须要一致,部分Qos要兼容。一旦Puiblisher和Subscriber匹配上之后,就可以接收和发送用户数据了。

数据结构

ParticipantProxyData

服务发现的对端的信息都存储在ParticipantProxyData中。这个实例在发现过程中被创建,然后填充发现后的信息后,加入到PDP的参与者列表(ResourceLimitedVector<ParticipantProxyData*> participant_proxies_)中。当前participant的信息构造的ParticipantProxyData对象作为participant_proxies_的第一个成员。

其他较重要的数据类型:

BuiltinAttributes m_discovery;  // 主要用于服务发现配置数据
std::unique_ptr<fastdds::rtps::PDPEndpoints> builtin_endpoints_;
EDP* mp_EDP;   // PDP 中指定要使用的EDP对象
ResourceLimitedVector<ParticipantProxyData*> participant_proxies_;  // 注册的RTPSParticipants对象,第一个元素为本地创建的RTPSParticipants,PDP阶段发现的RTPSParticipants都会添加到这个数据中
ResourceLimitedVector<ParticipantProxyData*> participant_proxies_pool_;//!Pool of participant proxy data objects ready for reuse
size_t reader_proxies_number_;//!Number of reader proxy data objects created
 ResourceLimitedVector<ReaderProxyData*> reader_proxies_pool_;//!Pool of reader proxy data objects ready for reuse
size_t writer_proxies_number_;//!Number of writer proxy data objects created
ResourceLimitedVector<WriterProxyData*> writer_proxies_pool_;//!Pool of writer proxy data objects ready for reuse
 std::atomic_bool m_hasChangedLocalPDP;//!Variable to indicate if any parameter has changed.
ReaderListener* mp_listener;//!Listener for the SPDP messages.
ProxyPool<ReaderProxyData> temp_reader_proxies_;//! ProxyPool for temporary reader proxies
ProxyPool<WriterProxyData> temp_writer_proxies_;//! ProxyPool for temporary writer proxies

Simple发现开发视图

类图

在这里插入图片描述

对象创建时序图

在这里插入图片描述

创建PDP对象

服务发现对象的创建在构造DomainParticipant时开始,具体来说在构造RTPSParticipantImpl时通过创建BuiltinProtocols对象并初始化时开始。
BuiltinProtocols::initBuiltinProtocols中构造一个PDPSimple对象,同时创建多个ParticipantProxyDataWriterProxyDataReaderProxyData对象,其中,ParticipantProxyData用于存放PDP发现找到的participant的数据,WriterProxyDataReaderProxyData用于存放EDP阶段需要发送给其他participant的writer和reader信息。

bool BuiltinProtocols::initBuiltinProtocols(
        RTPSParticipantImpl* p_part,
        BuiltinAttributes& attributes)
{
    mp_participantImpl = p_part;
    m_att = attributes;
    m_metatrafficUnicastLocatorList = m_att.metatrafficUnicastLocatorList;
    m_metatrafficMulticastLocatorList = m_att.metatrafficMulticastLocatorList;
    m_initialPeersList = m_att.initialPeersList;
    ...
    // PDP
    switch (m_att.discovery_config.discoveryProtocol)
    {
        case DiscoveryProtocol_t::NONE:
            EPROSIMA_LOG_WARNING(RTPS_PDP, "No participant discovery protocol specified");
            return true;

        case DiscoveryProtocol_t::SIMPLE:
            mp_PDP = new PDPSimple(this, allocation);
            break;
		...
}

PDP对象初始化

这部分在PDPSimple::init中实现,可以分为两部分,一部分是创建PDP的其他相关对象,在PDP::initPDP中实现,另一部分创建EDPSimple对象

bool PDPSimple::init(
        RTPSParticipantImpl* part)
{
    // 第一部分: 初始化PDP
    // The DATA(p) must be processed after EDP endpoint creation
    if (!PDP::initPDP(part))
    ...
    //第二部分:构造EDPSimple对象
    if (m_discovery.discovery_config.use_STATIC_EndpointDiscoveryProtocol)
    {
        mp_EDP = new EDPStatic(this, mp_RTPSParticipant);
        if (!mp_EDP->initEDP(m_discovery))
        ...
    }
    else if (m_discovery.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol)
    {
        mp_EDP = new EDPSimple(this, mp_RTPSParticipant);
        if (!mp_EDP->initEDP(m_discovery))
        ...

initPDP

PDP相关对象如PDP endpoints,当前Participant的ParticipantProxyData等对象的创建,在PDP::initPDP中实现,具体又可以分为三部分:对象创建,METATRAFFIC更新,ParticipantProxyData的创建。

bool PDP::initPDP(
        RTPSParticipantImpl* part)
{
    mp_RTPSParticipant = part;
    m_discovery = mp_RTPSParticipant->getAttributes().builtin;
    initial_announcements_ = m_discovery.discovery_config.initial_announcements;
    //CREATE ENDPOINTS
    if (!createPDPEndpoints())
    {
        return false;
    }
    //UPDATE METATRAFFIC.
    update_builtin_locators();

    mp_mutex->lock();
    ParticipantProxyData* pdata = add_participant_proxy_data(mp_RTPSParticipant->getGuid(), false, nullptr);
    mp_mutex->unlock();

    if (pdata == nullptr)
    {
        return false;
    }
    initializeParticipantProxyData(pdata);

    return true;
}
  1. 对象创建,在PDPSimple::createPDPEndpoints中实现:
bool PDPSimple::createPDPEndpoints()
{
    fastdds::rtps::SimplePDPEndpoints* endpoints = nullptr;
    ...
    {
        endpoints = new fastdds::rtps::SimplePDPEndpoints();
        endpoints->reader.listener_.reset(new PDPListener(this));
    }
    builtin_endpoints_.reset(endpoints);

    bool ret_val = create_dcps_participant_endpoints();
    ...
}

这段代码又可以细分为三点:

  • 创建PDP Endpoints。具体为
    • 创建SimplePDPEndpoints对象,包含两个成员:BuiltinReader<fastrtps::rtps::StatelessReader>BuiltinWriter<fastrtps::rtps::StatelessWriter>,用于收发SPDP的数据
    • 创建PDPListener对象,交给BuiltinWriter<fastrtps::rtps::StatelessWriter>用于监听其他Participant的PDP报文,PDPListener监听到数据后交给BuiltinWriter<fastrtps::rtps::StatelessWriter>处理
    • PDPSimple::create_dcps_participant_endpoints中创建PDP的StatelessReaderStatelessWriter,和其对应的ReaderHistoryWriterHistory,reader_entity_id/writer_entity_id,topic name(“DCPSParticipant”),申请payload内存,设置相关Qos,PDP发送地址(默认239.255.0.1:7400)等。
  1. 更新meta traffic, locator为本地IP:7412
void PDPSimple::update_builtin_locators()
{
    auto endpoints = static_cast<fastdds::rtps::SimplePDPEndpoints*>(builtin_endpoints_.get());
    mp_builtin->updateMetatrafficLocators(endpoints->reader.reader_->getAttributes().unicastLocatorList);
}
  1. 创建一个当前Participant的ParticipantProxyData对象并添加到participant_proxies_中,这个ParticipantProxyData是保存当前participant的信息发送给其他participant。

创建EDP对象

《TODO:EDP对象创建时序图》

EDP对象的创建也是在PDPSimple::init中实现,完成上述PDP::initPDP后开始创建EDP对象。

EDP初始化

EDP初始化的代码在EDPSimple::initEDP中完成,EDPSimple::initEDP中主要操作为调用EDPSimple::createSEDPEndpoints创建SEDP Endpoints。

bool EDPSimple::createSEDPEndpoints()
{
    ...
    publications_listener_ = new EDPSimplePUBListener(this);  // pub listener
    subscriptions_listener_ = new EDPSimpleSUBListener(this);  // sub listener

    if (m_discovery.discovery_config.m_simpleEDP.use_PublicationWriterANDSubscriptionReader)
    {
        if (!EDPUtils::create_edp_writer(mp_RTPSParticipant, "DCPSPublications", c_EntityId_SEDPPubWriter,
                writer_history_att, watt, publications_listener_, pub_writer_payload_pool_, publications_writer_))
    
        if (!EDPUtils::create_edp_reader(mp_RTPSParticipant, "DCPSSubscriptions", c_EntityId_SEDPSubReader,
                reader_history_att, ratt, subscriptions_listener_, sub_reader_payload_pool_, subscriptions_reader_))
    }

    if (m_discovery.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriter)
    {
        if (!EDPUtils::create_edp_reader(mp_RTPSParticipant, "DCPSPublications", c_EntityId_SEDPPubReader,
                reader_history_att, ratt, publications_listener_, pub_reader_payload_pool_, publications_reader_))
                
        if (!EDPUtils::create_edp_writer(mp_RTPSParticipant, "DCPSSubscriptions", c_EntityId_SEDPSubWriter,
                writer_history_att, watt, subscriptions_listener_, sub_writer_payload_pool_, subscriptions_writer_))
    }
}

这段代码主要创建了两对EDP的writer/reader,以及对应的listener和WriterHistory/ReaderHistory

  1. 第一对,PublicationWriter和SubscriptionReader
  • PublicationWriter: topic name为DCPSPublications,listener为EDPSimplePUBListener,entity ID为c_EntityId_SEDPPubWriter。这个RTPSWriter用于在创建了DataWriter之后发送这个DataWriter的EDP的报文
    在发送EDP报文时,会指定EDP endpoint为publications_writer_,这段代码的实现在EDPSimple::processLocalWriterProxyData中。同时,接受端的reader entity ID为c_EntityId_SEDPPubReader(这一步在收到PDP消息后更新EDP对象时指定,具体参考这篇博客)。

  • SubscriptionReader:topic name为DCPSSubscriptions,listener为EDPSimpleSUBListener,entity ID为c_EntityId_SEDPSubReader。用于接收对端是DataReader时发送的EDP报文

  1. 第二对,PublicationReader和SubscriptionWriter
  • PublicationReader:topic name为DCPSPublications,listener为EDPSimplePUBListener,entity ID为c_EntityId_SEDPPubReader。用于接收对端是DataWriter时发送的EDP报文。
  • SubscriptionWriter:topic name为DCPSSubscriptions,listener为EDPSimpleSUBListener,entity ID为c_EntityId_SEDPSubWriter。这个RTPSWriter用于在创建了DataReader之后发送这个DataReader的EDP报文。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值