目录
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对象在什么地方被初始化?
这些内置协议包括:
- 参与者发现协议(Participant Discovery Protocol):该协议让参与者(即DDS应用)可以发现网络中的其他参与者。
- 发布者/订阅者发现协议(Publisher/Subscriber Discovery Protocol):这允许参与者发现网络中其他参与者的发布者和订阅者。
- 读写器发现协议(Reader/Writer Discovery Protocol):这使参与者可以发现网络中其他参与者的数据读写器。
- WLP保活协议(心跳,WriterLiveliness),主要由类WLP来实现
这些内置协议在Fast DDS中是自动启动并运行的,而BuiltinProtocols类就是用来管理和处理这些内置协议的。总的来说,BuiltinProtocols类的主要作用是管理和处理Fast DDS的内置协议,这些协议使得DDS应用可以在网络中发现和与其他应用交互。
发现机制
Simple Discovery
优点
- 简单易用,只要在同一网络中的节点启动Fast DDS应用,他就能自动发现彼此并建立连接
- 自动化,无需手动配置每个节点的网络地址,减轻了配置和维护的工作量
- 实时性,可以快速探测和连接节点,适合实时性要求高的系统
缺点
- 缺乏灵活性,不支持定制化的发现策略,可能不适合某些特殊需求
- 可扩展性问题:在大型复杂的系统中,需要所有节点之间进行广播,当节点数量非常多时可能会导致网络拥塞,所以缺乏可扩展性
- 安全性问题:Simple Discovery并未考虑到安全性问题,可能会导致节点攻击
适用场景
Static Discovery
Static Discovery使用SPDP(即上面的Simple Participant Discovery Protocol)作为PDP阶段,但跳过了SEDP,所有参与的节点在启动前必须已知。这些信息定义在XML文件中,即EDP阶段通过静态配置来代替SEDP协议。这在处理有限带宽和已知的DataWrites和DataReaders方面非常有用。如果所有的DataWriters和DataReaders,它们的Topics,data types都是事先已知的,EDP阶段可以用对端的静态配置替换。因此也只有在对端配置的部分才可以通信。
优点
- 安全。与Simple Discovery相比,Static Discovery可以更好的控制哪些节点可以连接到系统,从而提高系统的安全性
- 可扩展。不依赖于节点之间的广播,所以有较好的扩展性
- 灵活性。可以控制哪些节点应该连接在一起,使用更加灵活
缺点
- 需要手动配置每个节点的网络地址,增加了配置和维护的工作量;且如果拓扑结构发生变化,也需要手动修改
- 启动阶段,因为在系统启动阶段需要保证配置完成,否则可能导致启动失败或部分功能无法使用,导致启动较慢
适用场景
Discovery Server
这种发现机制使用中心化的策略,基于client-server模式。这种服务模式中,一个或多个DomainParticipants作为server,所有的其他节点作为client,向server注册信息,并从server获取其他节点的信息。
Discovery Server DomainParticipants可以是clients或者servers。分为四种模式:SERVER
,CLIENT
,BACKUP
,SUPPER CLIENT
。它们之间的区别主要是节点发现过程中的角色和职责上:
SERVER
:SERVER是Discovery Server模式中的中心节点,它负责管理和存储所有节点(包括CLIENT,BACKUP,SUPER CLIENT)的元数据信息。当一个CLIENT节点需要发现其他节点信息时,它会像SERVER请求所需的元数据信息。具体来说,一个SERVER会发布clients和servers的信息,同时一个server可能会连接到另一个serser上来接收它的clients信息。CLIENT
:CLIENT是需要与其他节点进行交互的节点。在Discovery Server模式中,CLIENT节点会连接到一个或多个SERVER,将自己的元数据信息注册到SERVER,并从SERVER获取它需要的,其他节点的元数据信息,以便与其他节点建立连接和进行通信。Clients需要预先知道它要连接的Servers。BAKCUP
:备份是一种特殊的SERVER,它会将发现的信息写入持久化的数据库,用于Server发生故障时提供备份和恢复服务。BACKUP节点会定期从SERVER同步所有节点的元数据信息。如果主SERVER发生故障,CLIENT节点可以从BACKUP节点获取所需要的元数据信息,以便于继续与其他节点通信。SUPER CLIENT
:特殊的CLIENT,可以直接与SERVER进行通信,而不需要通过BACKUP节点。这使得SUPER CLIENT可以在SERVER发生故障时直接从BACKUP节点获取元数据信息,而不需要等待BACKUP节点的同步。这可以提供系统的响应速度和可靠性。
优点
- 可扩展:对于大型系统,在Simple Discovery中每个节点都要互相通信,节点数量很大的情况下会造成大量通信从而导致网络拥塞,而在Discovery Server中节点之间的通信减少,大大减少了网络流量和节点处理负载
- 效率:点对点发现模式中,节点发现过程的时间通常会随着节点数量的增加而增加,而在Discovery Server模式中,由于所有的元素据都在服务器上,因此节点发现过程的时间基本上上固定的,不会随着节点的增加而增加。
- 灵活:Discovery Server提供一种更灵活的方式来管理拓扑结构,例如,节点可以部署到不同的网络子网中,只要可以访问到服务器即可,服务器可以根据需要来控制哪些客户端
缺点
- 服务器单点故障:如果发生故障,会导致所有节点无法发现和通信。虽然可以通过备份服务器来提高系统的可靠性,但会增加系统的复杂性和成本。
- 依赖服务器:所有节点都依赖服务器进行发现和通信,如果性能不足或者网络连接不稳定,整个系统的性能和稳定性都会受到影响
- 配置复杂:相比点到点的发现,需要配置服务器地址,端口,以及节点的角色和权限等信息。
适用场景
适用于大型和复杂的分布式系统
Manual Discovery
这种机制只与RTPS层兼容。没有PDP阶段,因此也就没有EDP阶段,所有匹配只能通过RTPS层的addReaderLocator, addReaderProxy, addWriterProxy方法
发现阶段
服务发现的创建和发现都是在BuiltinProtocols类中处理。所有的服务发现都可以分为两个阶段:
- Participant Discovery Phase(PDP):这个阶段DomainParticipant通过周期性的广播消息获取互相的存在。
- 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
对象,同时创建多个ParticipantProxyData
,WriterProxyData
和ReaderProxyData
对象,其中,ParticipantProxyData
用于存放PDP发现找到的participant的数据,WriterProxyData
和ReaderProxyData
用于存放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;
}
- 对象创建,在
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的StatelessReader
和StatelessWriter
,和其对应的ReaderHistory
和WriterHistory
,reader_entity_id/writer_entity_id,topic name(“DCPSParticipant”),申请payload内存,设置相关Qos,PDP发送地址(默认239.255.0.1:7400)等。
- 创建
- 更新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);
}
- 创建一个当前Participant的
ParticipantProxyData
对象并添加到participant_proxies_
中,这个ParticipantProxyData
是保存当前participant的信息发送给其他participant。
创建EDP对象
《TODO:EDP对象创建时序图》
EDP对象的创建也是在PDPSimple::init
中实现,完成上述PDP::initPDP
后开始创建EDP对象。
EDP初始化
EDP初始化的代码在EDPSimple::init
EDP中完成,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
- 第一对,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报文
- 第二对,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报文。