Fast DDS 作为数据分发服务(DDS)的一种实现,提供了发现机制,能够自动查找并匹配不同 DomainParticipant 中的 DataWriter 和 DataReader,从而使它们能够开始共享数据。对于所有机制而言,这种发现过程都分两个阶段进行。
5.1 发现阶段
- **参与者发现阶段(PDP):**在此阶段,DomainParticipant 互相确认对方的存在。为此,每个 DomainParticipant 会发送周期性的通告消息,这些消息指定了(除其他信息外)DomainParticipant 正在监听传入的元数据和用户数据流量的单播地址(IP 和端口)。当两个 DomainParticipant 存在于同一个 DDS 域中时,它们将会匹配。默认情况下,通告消息使用众所周知的组播地址和端口(使用 DomainId 计算得出)发送。此外,还可以指定使用单播发送通告的地址列表(参见《初始对等点》)。而且,还可以配置此类通告的发送周期(参见《发现配置》)。
- 端点发现阶段(EDP): 在此阶段,DataWriter 和 DataReader 互相确认对方。为此,DomainParticipant 使用在 PDP 期间建立的通信通道,相互分享关于它们的 DataWriter 和 DataReader 的信息。这些信息包含(除其他信息外)主题和数据类型(参见《主题》)。要使两个端点匹配,它们的主题和数据类型必须一致。一旦 DataWriter 和 DataReader 匹配成功,它们就可以开始发送/接收用户数据流量了。
可以使用 PDP 阶段来传输关于运行 DomainParticipant 的主机、用户和进程(物理信息)的信息。有关如何配置要传输的物理数据的更多信息,请参阅《发现信息中的物理数据》。
5.2 发现机制
Fast DDS 提供以下发现机制:
- **简单发现:**这是默认机制。它在 PDP 和 EDP 阶段都遵循 RTPS 标准,因此可与任何其他 DDS 和 RTPS 实现兼容。
- **静态发现:**此机制在 PDP 阶段使用简单参与者发现协议(SPDP)(符合 RTPS 标准),但当所有 DataWriter 和 DataReader 的 IP、端口、数据类型和主题都预先知道时,允许跳过简单端点发现协议(SEDP)阶段。
- **发现服务器:**此发现机制采用集中式发现架构,其中一个称为 Server 的 DomainParticipant 充当元流量发现的中枢。
- **手动发现:**此机制仅与 RTPS 层兼容。它会禁用 PDP,让用户使用其选择的任何外部元信息通道,手动匹配和取消匹配 RTPSParticipant、RTPSReader 和 RTPSWriter。因此,用户必须访问 DomainParticipant 实现的 RTPSParticipant,并直接匹配 RTPS 实体。
5.3 发现设置
以下各节列出并描述了前述每种发现机制可用的设置,以及如何定义 DomainParticipantListener 的发现回调。
5.3.1 通用发现设置
某些发现设置在不同的发现机制中是共用的。这些设置定义在 WireProtocolConfigQos 类的 builtin 公有数据成员下,包括:
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| 发现协议 | 要使用的发现协议(参见《发现机制》)。 | DiscoveryProtocol | SIMPLE |
| 忽略参与者标志 | 过滤同一进程、不同进程或不同主机中的 DomainParticipant 的发现流量。 | ParticipantFilteringFlags | NO_FILTER |
| 租约持续时间 | 指示远程 DomainParticipant 应将本地 DomainParticipant 视为存活的时间长度。 | Duration_t |
20 s |
| 通告周期 | DomainParticipant 发送 PDP 通告的周期。 | Duration_t |
3 s |
5.3.1.1 发现协议
指定要使用的发现协议(参见《发现机制》)。可能的取值如下:
| 发现机制 | 可能的取值 | 描述 |
|---|---|---|
| 简单 | SIMPLE | 按照 RTPS 标准规定的简单发现协议。 |
| 发现服务器 | SERVER | DomainParticipant 充当发现流量的枢纽,接收并分发发现信息。 |
| 发现服务器 | CLIENT | DomainParticipant 充当发现流量的客户端。它将自身的发现信息发送给服务器,并仅接收与其相关的信息。 |
| 发现服务器 | SUPER_CLIENT | DomainParticipant 充当发现流量的客户端。它将自身的发现信息发送给服务器,并从服务器接收所有其他发现信息。 |
| 发现服务器 | BACKUP | 创建一个具有持久化 SQLite 数据库的 SERVER 类型的 DomainParticipant。BACKUP 服务器可以在启动时加载数据库。这种服务器类型使发现服务器架构能够抵御服务器故障。 |
| 手动 | NONE | 禁用 PDP 阶段,因此也没有 EDP 阶段。所有匹配必须通过 RTPS 层的 addReaderLocator、addReaderProxy、addWriterProxy 方法手动完成。 |
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::SIMPLE;
5.3.1.2 忽略参与者标志
定义了一个过滤器,用于在接收到某些发现流量时忽略它们。这对于增加一层额外的 DomainParticipant 隔离非常有用。可能的取值如下:
| 可能的取值 | 描述 |
|---|---|
| NO_FILTER | 处理所有发现流量。 |
| FILTER_DIFFERENT_HOST | 丢弃来自其他主机的发现流量。 |
| FILTER_DIFFERENT_PROCESS | 丢弃同一主机上其他进程的发现流量。 |
| FILTER_SAME_PROCESS | 丢弃来自 DomainParticipant 自身进程的发现流量。 |
| FILTER_DIFFERENT_PROCESS | 来自 DomainParticipant 自身主机的发现流量被丢弃。 |
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.ignoreParticipantFlags =
static_cast<eprosima::fastdds::rtps::ParticipantFilteringFlags>(
ParticipantFilteringFlags::FILTER_DIFFERENT_PROCESS |
ParticipantFilteringFlags::FILTER_SAME_PROCESS);
要将 DomainParticipant 配置为不接收来自其自身 DataWriter 的数据,请参阅《忽略本地端点》。
5.3.1.3 租约持续时间
指示远程 DomainParticipant 应将本地 DomainParticipant 视为存活的时间长度。如果在此时间内本地 DomainParticipant 的活性未被断言,则远程 DomainParticipant 会认为本地 DomainParticipant 已死亡,并销毁与本地 DomainParticipant 及其所有端点相关的全部信息。
每当远程 DomainParticipant 从本地 DomainParticipant 接收到任何类型的通信时,本地 DomainParticipant 的活性就会在远程 DomainParticipant 上得到断言。
租约持续时间使用 Duration_t 以秒和纳秒表示的时间来指定。
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.leaseDuration = Duration_t(10, 20);
5.3.1.4 通告周期
它指定了 DomainParticipant 的 PDP 通告的周期。为了维持活性,建议通告周期短于租约持续时间,以便即使在没有数据通信的情况下,DomainParticipant 的活性也能得到断言。需要注意的是,设置通告周期存在一个权衡,即通告过于频繁会使网络充斥元流量,但通告过于稀少则会延迟后来加入者的发现。
DomainParticipant 的通告周期使用 Duration_t 以秒和纳秒表示的时间来指定。
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.leaseDuration_announcementperiod = Duration_t(1, 2);
5.3.2 简单发现设置
SIMPLE 发现协议负责解决各种 DDS 实体之间端到端连接的建立问题。eProsima Fast DDS 实现了 SIMPLE 发现协议,以提供与 RTPS 标准的兼容性。该规范将 SIMPLE 发现协议分为两个独立的协议:
- 简单参与者发现协议(SPDP):规定了 DomainParticipant 如何在网络中相互发现;它通告并检测同一域内 DomainParticipant 的存在。
- 简单端点发现协议(SEDP):定义了已发现的 DomainParticipant 之间为发现各自包含的 DDS 实体(即 DataWriter 和 DataReader)而交换信息所采用的协议。
| 名称 | 描述 |
|---|---|
| 初始通告 | 定义 DomainParticipant 初始通告的行为。 |
| 简单 EDP 属性 | 定义将 SIMPLE 协议用作发现协议时的相关属性。 |
| 初始对等点 | 发送 SPDP 通告的目标 DomainParticipant 的 IP/端口对列表。 |
5.3.2.1 初始通告
RTPS 标准的简单发现机制要求 DomainParticipant 发送它们存在于域中的通告。这些通告不以可靠的方式传递,并且可能被网络丢弃。为了避免因消息丢弃导致的发现延迟,可以设置初始通告进行多次发送,以增加被正确接收的机会。参见 InitialAnnouncementConfig。
初始通告仅在参与者创建时发生。此阶段结束后,唯一强制执行的通告是基于 leaseDuration 和 announcementPeriod 周期(而非 period)的标准通告。
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| count | 定义启动时要发送的通告数量。 | uint32_t |
5 |
| period | 定义初始通告的具体周期。 | Duration_t |
100ms |
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.initial_announcements.count = 5;
pqos.wire_protocol().builtin.discovery_config.initial_announcements.period = Duration_t(0, 100000000u);
5.3.2.2 简单 EDP 属性
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| 简单 EDP(SIMPLE EDP) | 定义使用 SIMPLE 协议作为 EDP 阶段的发现协议。DomainParticipant 可以创建 DataWriter、DataReader、两者都创建或两者都不创建。 | bool |
true |
| 发布写入器和订阅读取器(Publication writer and Subscription reader) | I适用于仅实现一个或多个 DataWriter(即未实现 DataReader)的 DomainParticipant。它允许仅创建与 DataReader 发现相关的 EDP 端点。 | bool |
true |
| 发布读取器和订阅写入器(Publication reader and Subscription writer) | I适用于仅实现一个或多个 DataReader(即未实现 DataWriter)的 DomainParticipant。它允许仅创建与 DataWriter 发现相关的 EDP 端点。 | bool |
true |
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = true;
pqos.wire_protocol().builtin.discovery_config.m_simpleEDP.use_PublicationWriterANDSubscriptionReader = true;
pqos.wire_protocol().builtin.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriter = false;
5.3.2.3 初始对等点
根据 RTPS 标准(第 9.6.1.1 节),每个 RTPSParticipant 必须监听两个不同端口上的传入参与者发现协议(PDP)发现元流量:一个端口链接到组播地址,另一个端口链接到单播地址。Fast DDS 允许配置一个初始对等点列表(initial peers list),该列表包含一个或多个对应于远程 DomainParticipant 的 PDP 发现监听资源的 IP-端口地址对,这样本地 DomainParticipant 将仅将其 PDP 流量发送到初始对等点列表中指定的 IP-端口地址对。
DomainParticipant 的初始对等点列表包含了它将与之通信的所有其他 DomainParticipant 的 IP-端口地址对列表。它是 DomainParticipant 在 PDP 发现机制中将使用的地址列表,可以包含组播和单播地址。如果列表为空,将使用默认的组播地址。因此,这种方法也适用于那些组播功能不可用的场景。
根据 RTPS 标准(第 9.6.1.1 节),RTPSParticipant 的发现流量单播监听端口使用以下公式计算:7400 + 250 domainID + 10 + 2 participantID。因此,例如,如果一个 RTPSParticipant 在域 0(默认域)中运行且其 ID 为 1,则其发现流量单播监听端口将是:7400 + 250 0 + 10 + 2 1 = 7412。默认情况下,eProsima Fast DDS 使用元流量组播定位器作为初始对等点。
以下示例配置了一个初始对等点列表,其中包含一个位于主机 192.168.10.13、域为 0、DomainParticipant ID 为 1 的对等点。
还有一种可能性是不定义初始对等点的端口。在这种情况下,发现信息将被发送到从 participantID 为零到 TransportDescriptorInterface 中设置的 maxInitialPeersRange 值范围内的每个端口。因此,将此值至少设置为预期的 DomainParticipant 最大数量将确保发现和通信。
DomainParticipantQos qos;
// configure an initial peer on host 192.168.10.13.
// The port number corresponds to the well-known port for metatraffic unicast
// on participant ID `1` and domain `0`.
Locator_t initial_peer;
IPLocator::setIPv4(initial_peer, "192.168.10.13");
initial_peer.port = 7412;
qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer);
5.3.3 静态发现设置
Fast DDS 允许将 EDP 阶段的 SEDP 协议替换为静态版本,从而完全消除 EDP 元流量。这在处理有限网络带宽以及已知 DataWriter 和 DataReader 模式时非常有用。如果所有 DataWriter、DataReader 及其主题和数据类型都是预先已知的,则 EDP 阶段可以替换为静态配置的对等点。需要注意的是,这样做将不会产生 EDP 发现元流量,并且只有配置中定义的那些对等点才能进行通信。与静态发现相关的设置如下:
| 名称 | 描述 |
|---|---|
| 静态 EDP | 激活静态发现协议。 |
| 静态 EDP XML 配置规范 | 指定包含远程 DataWriter 和 DataReader 描述的 XML 内容。 |
| 初始通告 | 定义 DomainParticipant 初始通告(PDP 阶段)的行为。 |
5.3.3.1 静态 EDP
要激活静态 EDP,必须在 WireProtocolConfigQos 中禁用 SEDP。这可以通过代码或使用 XML 配置文件来完成:
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = false;
pqos.wire_protocol().builtin.discovery_config.use_STATIC_EndpointDiscoveryProtocol = true;
目前支持两种在参与者发现阶段(PDP)交换信息的格式:默认格式和另一种减少网络带宽使用的格式。《静态发现的交换格式》说明了如何更改此设置。
5.3.3.2 静态 EDP XML 配置规范
由于激活静态 EDP 会抑制所有 EDP 元流量,因此必须静态指定关于远程实体(DataWriter 和 DataReader)的信息,这通过专用的 XML 文件来完成。DomainParticipant 可以加载多个此类配置文件,以便将不同实体的信息包含在一个文件中,或拆分为不同的文件以保持更有序的结构。Fast DDS 提供了一个实现此 EDP 发现协议的《静态发现示例》。
下表描述了静态 EDP XML 配置文件的所有可能元素。此类文件的完整示例可在《静态 EDP XML 示例》中找到。
| 名称 | 描述 | 取值 | 默认值 |
|---|---|---|---|
<userId> |
必需。唯一标识 DataReader/DataWriter。 | uint16_t |
0 |
<entityID> |
DataReader/DataWriter 的 EntityId。 | uint16_t |
0 |
<expects_inline_qos> |
指示是否期望内联 QoS(仅限 DataReader)。 | bool |
false |
<topicName> |
必需。远程 DataReader/DataWriter 的主题。应与本地 DataReader/DataWriter 的某个主题匹配。 | string_255 |
|
<topicDataType> |
必需。主题的数据类型。 | string_255 |
|
<topicKind> |
主题的类型。 | NO_KEY WITH_KEY |
NO_KEY |
<partitionQos> |
远程对等点的分区名称。重复配置多个分区。 | string |
|
<unicastLocator> |
DomainParticipant 的单播定位器。参见《定位器定义》。 | ||
<multicastLocator> |
DomainParticipant 的组播定位器。参见《定位器定义》。 | ||
<reliabilityQos> |
参见《ReliabilityQosPolicy》部分。 | BEST_EFFORT_ RELIABILITY_QOS RELIABLE_ RELIABILITY_QOS | BEST_EFFORT_ RELIABILITY_QOS |
<durabilityQos> |
参见《DurabilityQosPolicy》部分。 | VOLATILE_ DURABILITY_QOS TRANSIENT_LOCAL_ DURABILITY_QOS TRANSIENT_ DURABILITY_QOS | VOLATILE_ DURABILITY_QOS |
<ownershipQos> |
参见《Ownership QoS》。 | ||
<livelinessQos> |
定义远程对等点的活性。参见《Liveliness QoS》。 | ||
<disablePositiveAcks> |
参见《DisablePositiveACKs QosPolicy》。 | 参见《DisablePositiveAcks》 |
5.3.3.2.1 定位器定义
远程对等点的定位器使用 <unicastLocator> 和 <multicastLocator> 标签进行配置。这些标签本身不取值,定位器通过其子标签元素定义。使用 <unicastLocator> 和 <multicastLocator> 定义的定位器是可累加的,因此可以重复使用它们来为同一个对等点分配多个远程端点定位器。
- address:一个必需的字符串,表示定位器地址。
- port:一个可选的 uint16_t 类型值,表示该地址上的端口。
5.3.3.2.2 所有权 QoS
主题的所有权可以使用 <ownershipQos> 标签进行配置。该标签本身不取值,配置通过其子标签元素完成:
- kind:可以是 SHARED_OWNERSHIP_QOS 或 EXCLUSIVE_OWNERSHIP_QOS。此元素在标签内是必需的。
- strength:一个可选的 uint32_t 类型值,指定远程 DomainParticipant 拥有该主题的强度。此 QoS 只能在 DataWriter 上设置。如果未指定,默认值为零。
5.3.3.2.3 活性 QoS
远程对等点的 LivelinessQosPolicy 使用 <livelinessQos> 标签进行配置。该标签本身不取值,配置通过其子标签元素完成:
- kind:可以是 AUTOMATIC_LIVELINESS_QOS、MANUAL_BY_PARTICIPANT_LIVELINESS_QOS 或 MANUAL_BY_TOPIC_LIVELINESS_QOS 中的任意一个。此元素在标签内是必需的。
- leaseDuration_ms:一个可选的 uint32 类型值,指定远程对等点的租约持续时间。可以使用特殊值 INF 表示无限租约持续时间。如果未指定,默认值为 INF。
5.3.3.3 检查静态 EDP XML 文件
在加载静态 EDP XML 文件之前,检查其有效性并确保文件能被成功加载是非常有用的。可以在 DomainParticipantFactory 上使用 DomainParticipantFactory::check_xml_static_discovery() 执行此验证,可以传入 XML 文件或直接传入配置,如下例所示。
// The (file://) flag is optional.
std::string file = "file://static_Discovery.xml";
DomainParticipantFactory* factory = DomainParticipantFactory::get_instance();
if (RETCODE_OK != factory->check_xml_static_discovery(file))
{
std::cout << "Error parsing xml file " << file << std::endl;
}
5.3.3.3.1 静态 EDP XML 示例
以下是一个用于两个远程 DomainParticipant(一个 DataWriter 和一个 DataReader)的完整配置 XML 文件示例。此配置必须与创建远程 DataReader/DataWriter 时使用的配置一致。否则,DataReader 和 DataWriter 之间的通信可能会受到影响。如果缺少任何非必需元素,将采用默认值。一个经验法则是,创建远程 DataReader/DataWriter 时指定的所有元素都应在此配置。
<staticdiscovery>
<participant>
<name>HelloWorldSubscriber</name>
<reader>
<userId>3</userId>
<entityID>4</entityID>
<expects_inline_qos>true</expects_inline_qos>
<topicName>HelloWorldTopic</topicName>
<topicDataType>HelloWorld</topicDataType>
<topicKind>WITH_KEY</topicKind>
<partitionQos>HelloPartition</partitionQos>
<partitionQos>WorldPartition</partitionQos>
<unicastLocator address="192.168.0.128" port="5000"/>
<unicastLocator address="10.47.8.30" port="6000"/>
<multicastLocator address="239.255.1.1" port="7000"/>
<reliabilityQos>BEST_EFFORT_RELIABILITY_QOS</reliabilityQos>
<durabilityQos>VOLATILE_DURABILITY_QOS</durabilityQos>
<ownershipQos kind="SHARED_OWNERSHIP_QOS"/>
<livelinessQos kind="AUTOMATIC_LIVELINESS_QOS" leaseDuration_ms="1000"/>
<disablePositiveAcks>
<enabled>true</enabled>
</disablePositiveAcks>
</reader>
</participant>
<participant>
<name>HelloWorldPublisher</name>
<writer>
<unicastLocator address="192.168.0.120" port="9000"/>
<unicastLocator address="10.47.8.31" port="8000"/>
<multicastLocator address="239.255.1.1" port="7000"/>
<userId>5</userId>
<entityID>6</entityID>
<topicName>HelloWorldTopic</topicName>
<topicDataType>HelloWorld</topicDataType>
<topicKind>WITH_KEY</topicKind>
<partitionQos>HelloPartition</partitionQos>
<partitionQos>WorldPartition</partitionQos>
<reliabilityQos>BEST_EFFORT_RELIABILITY_QOS</reliabilityQos>
<durabilityQos>VOLATILE_DURABILITY_QOS</durabilityQos>
<ownershipQos kind="SHARED_OWNERSHIP_QOS" strength="50"/>
<livelinessQos kind="AUTOMATIC_LIVELINESS_QOS" leaseDuration_ms="1000"/>
<disablePositiveAcks>
<enabled>true</enabled>
<duration>
<sec>300</sec>
</duration>
</disablePositiveAcks>
</writer>
</participant>
</staticdiscovery>
5.3.3.4 加载静态 EDP XML 文件
静态发现的远程 DataReader/DataWriter 必须在其配置文件中定义一个唯一的 userID,其值必须与发现配置 XML 中指定的值一致。这是通过在 DataReaderQos/DataWriterQos 上设置用户 ID 来完成的:
// Configure the DataWriter
DataWriterQos wqos;
wqos.endpoint().user_defined_id = 1;
// Configure the DataReader
DataReaderQos rqos;
rqos.endpoint().user_defined_id = 3;
在本地 DomainParticipant 上,您可以加载静态 EDP 配置内容,并指定包含该内容的文件。
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.static_edp_xml_config("file://RemotePublisher.xml");
pqos.wire_protocol().builtin.discovery_config.static_edp_xml_config("file://RemoteSubscriber.xml");
或者,您可以直接指定静态 EDP 配置内容。
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.static_edp_xml_config(
"data://<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
"<staticdiscovery><participant><name>RTPSParticipant</name></participant></staticdiscovery>");
5.3.4 发现服务器设置
该机制基于客户端-服务器发现范式,即元流量(DomainParticipant 之间为识别彼此而进行的消息交换)由一个或多个服务器类型的 DomainParticipant 管理(左图),这与简单发现(右图)不同,在简单发现中,元流量使用诸如 IP 组播协议之类的消息广播机制进行交换。提供了一个《发现服务器工具》以简化发现服务器的设置和测试。
当启用默认的发现服务器机制时,DDS 域概念不适用,但在使用 `ROS2_EASY_MODE` 时适用。

发现服务器与简单发现机制的对比
5.3.4.1 关键概念
在这种架构中,有几个需要理解的关键概念:
- 发现服务器机制重用了 RTPS 发现消息结构,以及标准的 DDS DataWriter 和 DataReader。
- 发现服务器类型的 DomainParticipant 可以是客户端或服务器。它们之间的唯一区别在于如何处理发现流量。用户流量,即由它们创建的 DataWriter 和 DataReader 之间的流量,与角色无关。
- SERVER 是一个参与者,客户端(可能还有其他服务器)向其发送它们的发现信息。
- 服务器的角色是将其客户端的发现信息重新分发给其已知的客户端和服务器。
- 服务器还会向其他已知服务器通告新服务器的存在,反之亦然。通过这种方式,新服务器只需知道网络中一个现有服务器的存在,即可连接到所有其他现有服务器。这样,只需最少的配置即可在服务器之间创建网状拓扑。
- 重新分发的发现信息可能直接来自连接到 SERVER 的客户端,也可能来自另一个正在转发其客户端发现数据的服务器。
- 已知的服务器将接收来自该服务器已知的直接客户端的所有信息以及其他服务器的参与者信息(以通告新服务器)。
- 已知的客户端将仅接收它们建立通信所需的信息,即关于它们匹配的 DomainParticipant、DataWriter 和 DataReader 的信息。这意味着服务器会运行一个"匹配"算法,以筛选出每个客户端需要的信息。
- BACKUP 服务器是一种将其发现数据库持久化到文件中的服务器。
- 这种类型的服务器可以在启动时从文件加载网络图,而无需接收任何客户端的信息。
- 它可以用于在运行之间持久化服务器对网络的认知,从而在意外关闭时保护服务器的信息。
- 需要注意的是,使用这种类型的服务器时,发现时间将受到负面影响,因为周期性地写入文件是一项开销较大的操作。
- CLIENT 是一种参与者,它连接到一个或多个服务器,并从服务器仅接收它们与匹配端点建立通信所需的发现信息。
- 客户端需要预先知道它们想要连接的服务器信息。基本上,它包含服务器正在监听的定位器列表,即一个 IP 地址和一个端口。这些定位器还定义了客户端将用于联系服务器的传输协议(UDP 或 TCP)。
- SUPER_CLIENT 是一种接收服务器已知的所有发现信息的客户端,这与普通客户端仅接收所需信息的行为相反。
SUPER_CLIENT 的行为与服务器不同,它仅通过其连接的服务器接收发现信息。它不会连接到其他服务器,也不会重新分发其接收到的信息。服务器发现的任何没有端点的 DomainParticipant 都不会被 SUPER_CLIENT 知晓。
- 服务器不需要预先知道其客户端的信息,但它们必须监听提供给客户端的定位器所指定的地址。客户端会定期(ping 周期)向服务器发送发现消息,直到收到消息接收确认。此后,服务器便会知道该客户端,并将相关的发现信息告知它。同样的原则也适用于服务器连接到另一台服务器。
5.3.4.2 在客户端和服务器之间进行选择
这是通过发现协议通用设置来确定的。一个参与者只能扮演一个角色(尽管一个服务器可以连接到其他服务器)。由于该值默认为 SIMPLE,因此必须填写此值。以下示例展示了如何通过编程方式和使用 XML 来设置此参数。
DomainParticipantQos pqos;
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::CLIENT;
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::SUPER_CLIENT;
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::SERVER;
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::BACKUP;
5.3.4.3 服务器定位器列表
每个服务器必须指定可以联系到它的有效定位器。任何客户端都必须被给予适当的定位器以联系其每个服务器。以下是服务器端和客户端设置的两个示例。
5.3.4.3.1 服务器端设置
以下示例展示了如何设置服务器定位器列表和 XML 标签。每个定位器必须包含:
-
IP 地址。
-
端口。
-
传输协议(UDPv4/6 或 TCPv4/6)。
Locator_t locator;
// The default locator kind is UDPv4
locator.kind = LOCATOR_KIND_UDPv4;
IPLocator::setIPv4(locator, 192, 168, 1, 133);
locator.port = 64863;DomainParticipantQos serverQos;
serverQos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(locator);
请注意,一个服务器可以连接到其他服务器,因此,以下部分也可能适用。
5.3.4.3.2 客户端设置
每个客户端必须维护一个与其想要连接的服务器相关联的定位器列表。
请注意,提供一个无法到达的定位器将导致客户端定期向该地址发送 ping 消息,直到它连接到与定位器列表中配置数量相同的服务器为止。
Locator_t locator;
// The default locator kind is UDPv4
locator.kind = LOCATOR_KIND_UDPv4;
IPLocator::setIPv4(locator, 192, 168, 1, 133);
locator.port = 64863;
DomainParticipantQos clientQos;
clientQos.wire_protocol().builtin.discovery_config.m_DiscoveryServers.push_back(locator);
此外,可以在定位器中指定一个逻辑端口。如果该参数留空,Fast DDS 会在需要时自动分配一个等于物理端口的逻辑端口。此行为与 ROS_DISCOVERY_SERVER 环境变量和 Fast DDS CLI 工具中实现的逻辑一致。
5.3.4.4 微调发现服务器握手
如上所述,客户端会定期(ping 周期)向服务器发送发现消息,直到它们收到与指定的远程定位器(服务器地址)数量相同的消息接收确认。请注意,此周期也适用于连接到其他服务器的服务器。此周期的默认值为 450 毫秒,但可以配置为不同的值。
DomainParticipantQos participant_qos;
participant_qos.wire_protocol().builtin.discovery_config.discoveryServer_client_syncperiod =
Duration_t(0, 250000000);
5.3.4.5 微调服务器发送速率限制器(Pro)
fastdds.discovery_server.send_period 属性控制将累积的发现变更连续刷新到写入器历史记录的最小间隔时间(以毫秒为单位)。当设置为一个正整数(例如,"1000" 表示一秒)时,服务器仍会在每次常规迭代中处理传入的数据(由 <clientAnnouncementPeriod> 触发),但会推迟发送步骤,直到距离上次刷新至少过了这么多毫秒。在该窗口期内累积的所有变更将一起批量发送。
这在许多参与者同时加入的大规模场景中非常有用:如果没有速率限制,服务器会在收到参与者所有端点之前发送部分发现数据,一旦剩余数据到达,就需要进行冗余重传。较长的发送周期允许变更累积,从而使发送的数据包含更完整的信息,减少总体流量。
5.3.4.6 将 GuidPrefix 用作可选的服务器唯一标识符
GuidPrefix_t 属性属于 RTPS 规范,用于唯一标识每个 RTPSParticipant。它由 12 个字节组成,在 Fast DDS 中是 DDS 域中使用的 DomainParticipant 的键。Fast DDS 将 DomainParticipant 的 GuidPrefix_t 定义为 WireProtocolConfigQos 类的公共数据成员。在新的发现服务器机制中,它完全是一个可选参数。然而,在与 Fast DDS v2.x 或更早版本的发现服务器实体一起操作的特定场景中,可能需要它,因为在这些版本中 GuidPrefix_t 是强制性的。
5.3.4.6.1 服务器端设置
以下示例展示了如何管理相应的枚举数据成员和 XML 标签。
/*******************C++ - Option 1**********************/
// Using the ``>>`` operator and the ``std::istringstream`` type.
DomainParticipantQos serverQos;
std::istringstream("44.53.00.5f.45.50.52.4f.53.49.4d.41") >> serverQos.wire_protocol().prefix;
/*******************C++ - Option 2**********************/
// Manual setting of the ``unsigned char`` in ASCII format.
eprosima::fastdds::rtps::GuidPrefix_t serverGuidPrefix;
serverGuidPrefix.value[0] = eprosima::fastdds::rtps::octet(0x44);
serverGuidPrefix.value[1] = eprosima::fastdds::rtps::octet(0x53);
serverGuidPrefix.value[2] = eprosima::fastdds::rtps::octet(0x00);
serverGuidPrefix.value[3] = eprosima::fastdds::rtps::octet(0x5f);
serverGuidPrefix.value[4] = eprosima::fastdds::rtps::octet(0x45);
serverGuidPrefix.value[5] = eprosima::fastdds::rtps::octet(0x50);
serverGuidPrefix.value[6] = eprosima::fastdds::rtps::octet(0x52);
serverGuidPrefix.value[7] = eprosima::fastdds::rtps::octet(0x4f);
serverGuidPrefix.value[8] = eprosima::fastdds::rtps::octet(0x53);
serverGuidPrefix.value[9] = eprosima::fastdds::rtps::octet(0x49);
serverGuidPrefix.value[10] = eprosima::fastdds::rtps::octet(0x4d);
serverGuidPrefix.value[11] = eprosima::fastdds::rtps::octet(0x41);
DomainParticipantQos serverQos;
serverQos.wire_protocol().prefix = serverGuidPrefix;
/*******************XML**********************/
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com">
<participant profile_name="participant_server_guidprefix" >
<rtps>
<prefix>44.53.00.5f.45.50.52.4f.53.49.4d.41</prefix>
</rtps>
</participant>
</profiles>
为服务器选择 GUID 前缀时,重要的是要考虑到 Fast DDS 也会使用此参数来识别同一主机或进程中的参与者,并将定位器转换为本地主机或启用进程内通信。建议让 Fast DDS 自动生成 GUID 前缀,以保证这些功能的正确行为。如果将两个 DomainParticipant 的 GUID 前缀设置为进程内兼容,但当它们运行在单独的进程中时,将无法进行通信。更多信息,请参阅《进程内交付的 GUID 前缀注意事项》。
使用相同的 GUID 前缀启动多个服务器是未定义的行为。
5.3.4.7 在运行时修改远程服务器列表
一旦服务器或客户端正在运行,可以通过编程方式修改运行中服务器或客户端应连接的远程服务器列表。这是通过调用 DomainParticipant::set_qos() 并传入一个包含修改后的 WireProtocolConfigQos 的 DomainParticipantQos 来实现的(参见 WireProtocolConfigQos)。此功能允许将新的远程服务器纳入发现服务器网络,或者在远程服务器使用不同的监听定位器重新启动时修改该远程服务器的定位器。
更新后的远程服务器列表将修改客户端或服务器的 ping 例程,但不会影响已建立的连接。因此,从列表中删除某个定位器并不会断开服务器或客户端与该远程服务器的连接。但是,如果连接中断,这将阻止重新连接。
远程服务器列表也可以使用 ROS_DISCOVERY_SERVER 环境变量进行修改。更多信息请参阅 FASTDDS_ENVIRONMENT_FILE。
强烈建议仅使用 API 或仅使用环境文件。同时使用两者可能会导致未定义的行为。
// Get existing QoS for the server or client
DomainParticipantQos client_or_server_qos;
client_or_server->get_qos(client_or_server_qos);
/* Create a new server entry to which the client or server should connect */
// Set server's listening locator for PDP
Locator_t locator;
IPLocator::setIPv4(locator, 127, 0, 0, 1);
locator.port = 11812;
/* Update list of remote servers for this client or server */
client_or_server_qos.wire_protocol().builtin.discovery_config.m_DiscoveryServers.push_back(locator);
if (RETCODE_OK != client_or_server->set_qos(client_or_server_qos))
{
// Error
return;
}
5.3.4.8 使用名称配置发现服务器定位器
在《发现服务器设置》中提供的所有示例都使用 IPv4 地址来指定服务器的监听定位器。然而,Fast DDS 也允许使用名称来指定定位器地址。
5.3.4.9 完整示例
以下是一个完整的示例,展示了如何通过编程方式和使用 XML 来配置服务器和客户端。您还可以查看 eProsima Fast DDS 的 Github 仓库,其中包含一个与本讨论类似的示例,以及针对不同用例的多个其他示例。
5.3.4.9.1 服务器端设置
// Get default participant QoS
DomainParticipantQos server_qos = PARTICIPANT_QOS_DEFAULT;
// Set participant as SERVER
server_qos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::SERVER;
// Set SERVER's listening locator for PDP
Locator_t locator;
IPLocator::setIPv4(locator, 127, 0, 0, 1);
locator.port = 11811;
server_qos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(locator);
/* Add a remote serve to which this server will connect */
// Set remote SERVER's listening locator for PDP
Locator_t remote_locator;
IPLocator::setIPv4(remote_locator, 127, 0, 0, 1);
remote_locator.port = 11812;
// Add remote SERVER to SERVER's list of SERVERs
server_qos.wire_protocol().builtin.discovery_config.m_DiscoveryServers.push_back(remote_locator);
// Create SERVER
DomainParticipant* server =
DomainParticipantFactory::get_instance()->create_participant(0, server_qos);
if (nullptr == server)
{
// Error
return;
}
5.3.4.9.2 客户端设置
// Get default participant QoS
DomainParticipantQos client_qos = PARTICIPANT_QOS_DEFAULT;
// Set participant as CLIENT
client_qos.wire_protocol().builtin.discovery_config.discoveryProtocol =
DiscoveryProtocol::CLIENT;
// Set SERVER's listening locator for PDP
Locator_t locator;
IPLocator::setIPv4(locator, 127, 0, 0, 1);
locator.port = 11811;
// Add remote SERVER to CLIENT's list of SERVERs
client_qos.wire_protocol().builtin.discovery_config.m_DiscoveryServers.push_back(locator);
// Set ping period to 250 ms
client_qos.wire_protocol().builtin.discovery_config.discoveryServer_client_syncperiod =
Duration_t(0, 250000000);
// Create CLIENT
DomainParticipant* client =
DomainParticipantFactory::get_instance()->create_participant(0, client_qos);
if (nullptr == client)
{
// Error
return;
}
5.3.4.10 安全机制(Pro)
在服务器和客户端上配置安全性的方式与任何其他参与者相同。本节描述了安全强制措施对客户端和服务器之间通信施加的限制,以及根据所连接的客户端和服务器的安全配置,服务器会传播哪些发现信息。
需要注意的是,为了在使用发现服务器时启用安全发现,Fast DDS 必须在编译时支持安全功能(参见 CMake 选项),并且域治理文档必须显式地加密发现过程。
与 SDP 一样,使用此功能时,连接到服务器的所有客户端和服务器的域治理文档必须与该服务器的域治理文档匹配,这意味着属于同一发现服务器网络的所有 DomainParticipant 必须以相同的方式配置发现保护。
尽管服务器介导了发现过程并建立了客户端之间的连接,但客户端本身仍然需要经过 PKI(公钥基础设施)交换,以便它们之间进行安全通信。
为了保持与 QoS 策略行为的一致性,服务器不会检查其正在连接的 DomainParticipant 的域参与者权限文档。
Security support for Discovery Server is only supported in Fast DDS Pro.
5.3.5 DomainParticipantListener 发现回调函数
如 DomainParticipantListener 中所述,DomainParticipantListener 是一个抽象类,定义了在响应 DomainParticipant 状态变化时将被触发的回调函数。Fast DDS 定义了三个与发现期间可能发生的事件相关联的回调:on_participant_discovery()、on_data_reader_discovery()、on_data_writer_discovery()。关于 DomainParticipantListener 的更多信息,请参阅 DomainParticipantListener 部分。以下是实现 DomainParticipantListener 发现回调的一个示例。
class DiscoveryDomainParticipantListener : public DomainParticipantListener
{
/* Custom Callback on_participant_discovery */
void on_participant_discovery(
DomainParticipant* participant,
eprosima::fastdds::rtps::ParticipantDiscoveryStatus status,
const ParticipantBuiltinTopicData& info,
bool& should_be_ignored) override
{
should_be_ignored = false;
static_cast<void>(participant);
switch (status){
case eprosima::fastdds::rtps::ParticipantDiscoveryStatus::DISCOVERED_PARTICIPANT:
{
/* Process the case when a new DomainParticipant was found in the domain */
std::cout << "New DomainParticipant '" << info.participant_name <<
"' with ID '" << info.guid.entityId << "' and GuidPrefix '" <<
info.guid.guidPrefix << "' discovered." << std::endl;
/* The following line can be substituted to evaluate whether the discovered participant should be ignored */
bool ignoring_condition = false;
if (ignoring_condition)
{
should_be_ignored = true; // Request the ignoring of the discovered participant
}
}
break;
case eprosima::fastdds::rtps::ParticipantDiscoveryStatus::CHANGED_QOS_PARTICIPANT:
/* Process the case when a DomainParticipant changed its QOS */
break;
case eprosima::fastdds::rtps::ParticipantDiscoveryStatus::REMOVED_PARTICIPANT:
/* Process the case when a DomainParticipant was removed from the domain */
std::cout << "DomainParticipant '" << info.participant_name <<
"' with ID '" << info.guid.entityId << "' and GuidPrefix '" <<
info.guid.guidPrefix << "' left the domain." << std::endl;
break;
}
}
/* Custom Callback on_data_reader_discovery */
void on_data_reader_discovery(
DomainParticipant* participant,
eprosima::fastdds::rtps::ReaderDiscoveryStatus reason,
const eprosima::fastdds::rtps::SubscriptionBuiltinTopicData& info,
bool& should_be_ignored) override
{
should_be_ignored = false;
static_cast<void>(participant);
switch (reason){
case eprosima::fastdds::rtps::ReaderDiscoveryStatus::DISCOVERED_READER:
{
/* Process the case when a new datareader was found in the domain */
std::cout << "New DataReader subscribed to topic '" << info.topic_name <<
"' of type '" << info.type_name << "' discovered";
/* The following line can be substituted to evaluate whether the discovered datareader should be ignored */
bool ignoring_condition = false;
if (ignoring_condition)
{
should_be_ignored = true; // Request the ignoring of the discovered datareader
}
}
break;
case eprosima::fastdds::rtps::ReaderDiscoveryStatus::CHANGED_QOS_READER:
/* Process the case when a datareader changed its QOS */
break;
case eprosima::fastdds::rtps::ReaderDiscoveryStatus::REMOVED_READER:
/* Process the case when a datareader was removed from the domain */
std::cout << "DataReader subscribed to topic '" << info.topic_name <<
"' of type '" << info.type_name << "' left the domain.";
break;
}
}
/* Custom Callback on_data_writer_discovery */
void on_data_writer_discovery(
DomainParticipant* participant,
eprosima::fastdds::rtps::WriterDiscoveryStatus reason,
const eprosima::fastdds::dds::PublicationBuiltinTopicData& info,
bool& should_be_ignored) override
{
should_be_ignored = false;
static_cast<void>(participant);
switch (reason){
case eprosima::fastdds::rtps::WriterDiscoveryStatus::DISCOVERED_WRITER:
{
/* Process the case when a new datawriter was found in the domain */
std::cout << "New DataWriter publishing under topic '" << info.topic_name <<
"' of type '" << info.type_name << "' discovered";
/* The following line can be substituted to evaluate whether the discovered datawriter should be ignored */
bool ignoring_condition = false;
if (ignoring_condition)
{
should_be_ignored = true; // Request the ignoring of the discovered datawriter
}
}
break;
case eprosima::fastdds::rtps::WriterDiscoveryStatus::CHANGED_QOS_WRITER:
/* Process the case when a datawriter changed its QOS */
break;
case eprosima::fastdds::rtps::WriterDiscoveryStatus::REMOVED_WRITER:
/* Process the case when a datawriter was removed from the domain */
std::cout << "DataWriter publishing under topic '" << info.topic_name <<
"' of type '" << info.type_name << "' left the domain.";
break;
}
}
};
要使用在从 DomainParticipantListener 继承的 DiscoveryDomainParticipantListener 类中先前实现的发现回调,需要创建此类的一个对象,并将其注册为 DomainParticipant 的监听器。
// Create the participant QoS and configure values
DomainParticipantQos pqos;
// Create a custom user DomainParticipantListener
DiscoveryDomainParticipantListener* plistener = new DiscoveryDomainParticipantListener();
// Pass the listener on DomainParticipant creation.
DomainParticipant* participant =
DomainParticipantFactory::get_instance()->create_participant(
0, pqos, plistener);