Discovery
=========
This section discusses the implementation of discovery plugins in |rti_me|.
For a general overview of discovery in |rti_me|, see :ref:`what_is_discovery`.
|me| discovery traffic is conducted through transports. Please see
the :doc:`transports/index` section for more information about registering
and configuring transports.
.. _what_is_discovery:
What is Discovery?
------------------
Discovery is the behind-the-scenes way in which |rti_me| objects
(*DomainParticipants*, *DataWriters*, and *DataReaders*) on different nodes
find out about each other. Each *DomainParticipant* maintains a database of
information about all the active *DataReaders* and *DataWriters* that are in
the same DDS domain. This database is what makes it possible for
*DataWriters* and *DataReaders* to communicate. To create and refresh the
database, each application follows a common discovery process.
This section describes the default discovery mechanism known as the
Simple Discovery Protocol, which includes
two phases: `Simple Participant Discovery`_ and `Simple Endpoint Discovery`_.
The goal of these two phases is to build, for each *DomainParticipant*,
a complete picture of all the entities that belong to the remote
participants that are in its peers list. The peers list is the list of
nodes with which a participant may communicate. It starts out the same as
the *initial_peers* list that you configure in the DISCOVERY_ QosPolicy.
If the accept_unknown_peers_ flag in that same QosPolicy is TRUE, then
other nodes may also be added as they are discovered; if it is FALSE,
then the peers list will match the initial_peers_ list, plus any peers
added using the *DomainParticipant’s* **add_peer()** operation.
The following section discusses how |me| objects on different nodes find
out about each other using the default Simple Discovery Protocol (SDP).
It describes the sequence of messages that are passed between |me| on the
sending and receiving sides.
Note that this chapter is shared between |me_micro| and |me_cert|. However
|me_cert| only supports static endpoint discovery described in
:ref:`static_endpoint_disc`.
The discovery process occurs automatically, so you do not have to implement
any special code. For more information about advanced topics related to
Discovery, please refer to |rti_core_um_discovery_verbose| in the
|rti_core_um| (available |rti_core_um_discovery|_ if you have Internet access).
Simple Participant Discovery
............................
This phase of the Simple Discovery Protocol is performed by the
Simple Participant Discovery Protocol (SPDP) and is common for
both dynamic and static endpoint discovery.
During the Participant Discovery phase, *DomainParticipants* learn about each
other. The *DomainParticipant*’s details are communicated to all other
*DomainParticipants* in the same DDS domain by sending participant
declaration messages, also known as participant *DATA* submessages. The
details include the *DomainParticipant*’s unique identifying key (GUID or
Globally Unique ID described below), transport locators (addresses and
port numbers), and QoS. These messages are sent on a periodic basis using
best-effort communication.
*Participant DATAs* are sent periodically to maintain the liveliness of
the *DomainParticipant*. They are also used to communicate changes in
the *DomainParticipant*’s QoS. Only changes to QosPolicies that are part
of the *DomainParticipant*’s built-in data need to be propagated.
When receiving remote participant discovery information, |me| determines
if the local participant matches the remote one. A ‘match’ between the local
and remote participant occurs only if the local and remote participant
have the same Domain ID. This matching process occurs as soon as the
local participant receives discovery information from the
remote one. If there is no match, the discovery DATA is ignored,
resulting in the remote participant (and all its associated entities)
not being discovered.
When a *DomainParticipant* is deleted, a participant *DATA (delete)*
submessage with the *DomainParticipant*'s identifying GUID is sent.
The GUID is a unique reference to an entity. It is composed of a GUID
prefix and an Entity ID. By default, the GUID prefix is calculated.
The entityID is set by |me| (you may be able to change it in a future
version).
.. only:: not cert
Once a pair of remote participants have discovered each other, they can move
on to the Endpoint Discovery phase, which is how *DataWriters* and
*DataReaders* find each other.
Simple Endpoint Discovery
.........................
This phase of the Simple Discovery Protocol is performed by the
Simple Endpoint Discovery Protocol (SEDP).
During the Endpoint Discovery phase, *DataWriters* and *DataReaders* are
*matched*. Information (GUID, QoS, etc.) about your application’s
*DataReaders* and *DataWriters* is exchanged by sending
publication/subscription declarations in DATA messages that we will
refer to as *publication DATAs* and *subscription DATAs*. The
Endpoint Discovery phase uses reliable communication.
These declarations or DATA messages are exchanged until each
*DomainParticipant* has a complete database of information about the
participants in its peers list and their entities. Then the discovery
process is complete and the system switches to a steady state.
During steady state, *participant DATAs* are still sent periodically
to maintain the liveliness status of participants. They may also be
sent to communicate QoS changes or the deletion of a *DomainParticipant*.
When a remote *DataWriter/DataReader* is discovered, |me_micro| determines
if the local application has a matching *DataReader/DataWriter*. A ‘match’
between the local and remote entities occurs only if the *DataReader* and
*DataWriter* have the same *Topic*, same data type, and compatible
QosPolicies. Furthermore, if the *DomainParticipant* has been set up to
ignore certain *DataWriters/DataReaders*, those entities will not be
considered during the matching process.
This ‘matching’ process occurs as soon as a remote entity is discovered,
even if the entire database is not yet complete: that is, the application
may still be discovering other remote entities.
A *DataReader* and *DataWriter* can only communicate with each other if
each one’s application has hooked up its local entity with the matching
remote entity. That is, both sides must agree to the connection.
Please refer to |rti_core_um_discoveryimplementation_verbose| in the
|rti_core_um| for more details about the discovery process (available
|rti_core_um_discoveryimplementation|_ if you have Internet access).
Configuring Participant Discovery Peers
---------------------------------------
An |rti_me| *DomainParticipant* must be able to send participant discovery
announcement messages for other *DomainParticipants* to discover itself,
and it must receive announcements from other *DomainParticipants* to
discover them.
To do so, each *DomainParticipant* will send its discovery announcements
to a set of locators known as its peer list, where a peer is the transport
locator of one or more potential other *DomainParticipants* to discover.
The Peer Address
................
A peer descriptor string of the initial_peers_ sequence defines
the interface and address of the locator to which to send, as well as the
indices of participants to which to send. The peer descriptor format is:
.. code-block:: none
< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
which means a literal.
ADDRESS =
|
@ |
INDEX@://
INDEX = INTEGER | '[' INTEGER ']' | '[' INTEGER-INTEGER ']' | '[' -INTEGER ']'
PREFIX = [a-zA-Z_][0-9a-zA-Z_]+
INTEGER = DEC_INTEGER | HEX_INTEGER
DEC_INTEGER = [0-9]+
HEX_INTEGER = [0x|0X][0-9a-fA-F]+
ADDRESS = 0 or more 8bit characters
Note that while the ``PREFIX`` is marked optional, it should always be used.
Remember that every *DomainParticipant* has a participant index that is
unique within a DDS domain. The participant index (also referred to as
the participant ID), together with the DDS domain ID, is used to calculate
the network ports on which *DataReaders* of that participant will receive
messages. Thus, by specifying the participant index, or a range of
indices, for a peer locator, that locator becomes one or more ports to
which messages will be sent only if addressed to the entities of a particular
*DomainParticipant*. Specifying indices restricts the number of participant
announcements sent to a locator where other *DomainParticipants* exist and,
thus, should be considered to minimize network bandwidth usage.
For example:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&dp_qos.discovery.initial_peers, 5);
DDS_StringSeq_set_length(&dp_qos.discovery.initial_peers, 5);
/* If the index is not specified, it defaults to 5, thus sending to
* the first 6 participant IDs on at IP address 192.168.1.1 using
* the transport registered as _udp.
*/
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 0) =
DDS_String_dup("_udp://192.168.1.1");
/* Only send participant annoucements to multicast address 239.255.0.1
* using the transport registered as _udp. Note that for multicast
* addresses the index is not relevant since it is a shared address.
*/
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 1) =
DDS_String_dup("_udp://239.255.0.1");
/* Send annoucements to participant ID 1,2,3, and 4 on 10.10.30.101
* using the transport registered as _udp.
*/
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 2) =
DDS_String_dup("[1-4]@_udp://10.10.30.101");
/* Send annoucements to participant ID 2 on address 10.10.30.102
* using the transport registered as _udp.
*/
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 3) =
DDS_String_dup("[2]@_udp://10.10.30.102");
/* Send annoucements to participant ID 0-8 on address 10.10.30.102
* using the transport registered as _udp.
*/
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 4) =
DDS_String_dup("8@_udp://10.10.30.102");
Configuring Initial Peers and Adding Peers
------------------------------------------
.. _DiscoveryQosPolicy_initial_peers: initial_peers_
DiscoveryQosPolicy_initial_peers_ is the list of peers a *DomainParticipant*
sends its participant announcement messages, when it is enabled, as part
of the discovery process.
DiscoveryQosPolicy_initial_peers_ is an empty sequence by default.
Peers can also be added to the list, before and after a *DomainParticipant*
has been enabled, by using DomainParticipant_add_peer_.
The *DomainParticipant* will start sending participant announcement messages
to the new peer as soon as it is enabled.
Configuring Discovery Data Reception
------------------------------------
In order to *receive* discovery and user data, it is necessary to configure the
``DomainParticipantQos.discovery.enabled_transports`` sequence. This is a
sequence of transport addresses to listen for discovery data on, and is sent
as part of the participant annoucements. Other *DomainParticipants* will
send to these addresses.
The address format for configuring data reception uses the following
format:
.. code-block:: none
< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
which means a literal.
ADDRESS =
PREFIX = [a-zA-Z_][0-9a-zA-Z_]+
ADDRESS = 0 or more 8bit characters
Note that while the ``PREFIX`` is marked optional, it should always be used.
For example, to receive on a single unicast address:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 1);
/* Receive on the unicast address 192.168.1.1 using the transport registered
* as _udp.
*/
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
DDS_String_dup("_udp://192.168.1.1");
To receive on all unicast addresses allowed by the transport:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 1);
/* Receive on all unicast addresses allowed by the transport registered
* as _udp. This is not recommended if more than 4 network interfaces are
* allowed as it is non-deterministic which interfaces will be used.
*/
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
DDS_String_dup("_udp://");
To receive on one unicast address and one multicast address:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 2);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 2);
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
DDS_String_dup("_udp://192.168.1.1");
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 1) =
DDS_String_dup("_udp://239.255.0.1");
To receive on one multicast address:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 1);
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
DDS_String_dup("_udp://239.255.0.1");
Configuring User Data Reception
-------------------------------
In order to *receive* discovery and user data, it is necessary to configure the
``DomainParticipantQos.user_traffic.enabled_transports`` sequence. This is a
sequence of default transport addresses to listen for user data on, unless
a *DataReader* or *DataWriter* specifies its own address, and is sent
as part of the participant annoucements. Other *DomainParticipants* will
send to these addresses.
The address format for configuring data reception uses the following
format:
.. code-block:: none
< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
which means a literal.
ADDRESS =
PREFIX = [a-zA-Z_][0-9a-zA-Z_]+
ADDRESS = 0 or more 8bit characters
Note that while the ``PREFIX`` is marked optional, it should always be used.
For example, to receive on a single unicast address:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 1);
/* Receive on the unicast address 192.168.1.1 using the transport registered
* as _udp.
*/
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
DDS_String_dup("_udp://192.168.1.1");
To receive on all unicast addresses allowed by the transport:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 1);
/* Receive on all unicast addresses allowed by the transport registered
* as _udp. This is not recommended if more than 4 network interfaces are
* allowed as it is non-deterministic which interfaces will be used.
*/
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
DDS_String_dup("_udp://");
To receive on one unicast address and one multicast address:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 2);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 2);
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
DDS_String_dup("_udp://192.168.1.1");
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 1) =
DDS_String_dup("_udp://239.255.0.1");
.. note::
When both multicast and unicast is specified, the following rules are used:
- New data is sent over multicast.
- Retransmissions are sent over unicast.
To receive on one multicast address:
.. code-block:: cpp
DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 1);
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
DDS_String_dup("_udp://239.255.0.1");
Configuring User Data Reception per DataReader or DataWriter
------------------------------------------------------------
A *DataReader* and *DataWriter* can specify its own addresses in the
``DataReaderQos.transport.enabled_transports`` and
``DataWriterQos.transport.enabled_transports`` policies. The address format is
exactly the same as for ``DomainParticipantQos.user_traffic.enabled_transports``,
with the restriction that a *DataWriter* can only specify its own unicast
addresses.
Discovery Plugins
-----------------
When a *DomainParticipant* receives a participant discovery message from
another *DomainParticipant*, it will engage in the process of exchanging
information of user-created *DataWriter* and *DataReader* endpoints.
|rti_me| provides two ways of determinig endpoint information of
other *DomainParticipants*: :ref:`dynamic_endpoint_disc`
and :ref:`static_endpoint_disc`.
.. _dynamic_endpoint_disc:
Dynamic Discovery Plugin
........................
.. only:: cert
.. note::
The Dynamic Discovery Plugin is not available in |me_cert|. The
description is only included for completeness and comparison with the
`Static Discovery Plugin`_.
Dynamic endpoint discovery uses builtin discovery *DataWriters* and
*DataReader* to exchange messages about user created *DataWriter* and
*DataReaders*. A *DomainParticipant* using dynamic participant, dynamic
endpoint (DPDE_) discovery will have a pair of builtin *DataWriters*
for sending messages about its own user created *DataWriters* and
*DataReaders*, and a pair of builtin *DataReaders* for receiving
messages from other *DomainParticipants* about their user created
*DataWriters* and *DataReaders*.
Given a *DomainParticipant* with a user *DataWriter*, receiving an
endpoint discovery message for a user *DataReader* allows the
*DomainParticipant* to get the type, topic, and QoS of the *DataReader*
that determine whether the *DataReader* is a match. When a
matching *DataReader* is discovered, the *DataWriter* will include
that *DataReader* and its locators as destinations for its subsequent
writes.
.. _static_endpoint_disc:
Static Discovery Plugin
.......................
Static endpoint discovery uses function calls to statically assert
information about remote endpoints belonging to remote *DomainParticipants*.
An application with a *DomainParticipant* using dynamic participant, static
endpoint (DPSE_) discovery has control over which endpoints belonging to
particular remote *DomainParticipants* are discoverable.
Whereas dynamic endpoint-discovery can establish matches for all
endpoint-discovery messages it receives, static endpoint-discovery
establishes matches *only* for the endpoint that have been asserted
programmatically. When a *DomainParticipant* receives a participant discovery
message from another *DomainParticipant*, it will engage in the process of
matching previously asserted user-created *DataWriter* and *DataReader*
endpoints.
With DPSE_, a user needs to know *a priori* the configuration of the
entities that will need to be discovered by its application. The user must
know the names of all *DomainParticipants* within the DDS domain and the
exact QoS of the remote *DataWriters* and *DataReaders*.
.. only:: not cert
Please refer to the |api_ref_c|_ and |api_ref_cpp|_ for the following
remote entity assertion APIs:
.. only:: cert
Please refer to the |api_ref_c|_ the following
remote entity assertion APIs:
- DPSE_RemoteParticipant_assert_
- DPSE_RemotePublication_assert_
- DPSE_RemoteSubscription_assert_
Remote Participant Assertion
''''''''''''''''''''''''''''
Given a local *DomainParticipant*, static discovery requires first the names
of remote *DomainParticipants* to be asserted, in order for endpoints on
them to match. This is done by calling DPSE_RemoteParticipant_assert_
with the name of a remote *DomainParticipant*. The name must match the name
contained in the participant discovery announcement produced by
that *DomainParticipant*. This has to be done reciprocally between
two *DomainParticipants* so that they may discover one another.
For example, a *DomainParticipant* has entity name "participant_1", while
another *DomainParticipant* has name "participant_2." participant_1
should call DPSE_RemoteParticipant_assert("participant_2") in order to
discover participant_2. Similarly, participant_2 must also assert
participant_1 for discovery between the two to succeed.
.. code-block:: cpp
/* participant_1 is asserting (remote) participant_2 */
retcode = DPSE_RemoteParticipant_assert(participant_1,
"participant_2");
if (retcode != DDS_RETCODE_OK) {
printf("participant_1 failed to assert participant_2\n");
goto done;
}
Remote Publication and Subscription Assertion
'''''''''''''''''''''''''''''''''''''''''''''
Next, a *DomainParticipant* needs to assert the remote endpoints it wants
to match that belong to an already asserted remote *DomainParticipant*. The
endpoint assertion function is used, specifying an argument which contains
all the QoS and configuration of the remote endpoint. Where DPDE_ gets
remote endpoint QoS information from received endpoint-discovery
messages, in DPSE_, the remote endpoint's QoS must be configured locally.
With remote endpoints asserted, the *DomainParticipant* then waits until
it receives a participant discovery announcement from an asserted
remote *DomainParticipant*. Once received that, all endpoints that have
been asserted for that remote *DomainParticipant* are considered
discovered and ready to be matched with local endpoints.
Assume participant_1 contains a *DataWriter*, and participant_2 has
a *DataReader*, both communicating on topic HelloWorld. participant_1
needs to assert the *DataReader* in participant_2 as a remote subscription.
The remote subscription data passed to the operation must match exactly
the QoS actually used by the remote *DataReader*:
.. code-block:: cpp
/* Set participant_2's reader's QoS in remote subscription data */
rem_subscription_data.key.value[DDS_BUILTIN_TOPIC_KEY_OBJECT_ID] = 200;
rem_subscription_data.topic_name = DDS_String_dup("Example HelloWorld");
rem_subscription_data.type_name = DDS_String_dup("HelloWorld");
rem_subscription_data.reliability.kind = DDS_RELIABLE_RELIABILITY_QOS;
/* Assert reader as a remote subscription belonging to (remote) participant_2 */
retcode = DPSE_RemoteSubscription_assert(participant_1,
"participant_2",
&rem_subscription_data,
HelloWorld_get_key_kind(HelloWorldTypePlugin_get(), NULL));
if (retcode != DDS_RETCODE_OK)
{
printf("failed to assert remote subscription\n");
goto done;
}
Reciprocally, participant_2 must assert participant_1's *DataWriter*
as a remote publication, also specifying matching QoS parameters:
.. code-block:: cpp
/* Set participant_1's writer's QoS in remote publication data */
rem_publication_data.key.value[DDS_BUILTIN_TOPIC_KEY_OBJECT_ID] = 100;
rem_publication_data.key.value.topic_name = DDS_String_dup("Example HelloWorld");
rem_publication_data.key.value.type_name = DDS_String_dup("HelloWorld");
rem_publication_data.key.value.reliability.kind = DDS_RELIABLE_RELIABILITY_QOS;
/* Assert writer as a remote publication belonging to (remote) participant_1 */
retcode = DPSE_RemotePublication_assert(participant_2,
"participant_1",
&rem_publication_data,
HelloWorld_get_key_kind(HelloWorldTypePlugin_get(), NULL));
if (retcode != DDS_RETCODE_OK)
{
printf("failed to assert remote publication\n");
goto done;
}
When participant_1 receives a participant discovery message from participant_2,
it is aware of participant_2, based on its previous assertion, and it knows
participant_2 has a matching *DataReader*, also based on the previous
assertion of the remote endpoint. It therefore establishes a match between
its *DataWriter* and participant_2's *DataReader*. Likewise, participant_2
will match participant_1's *DataWriter* with its local *DataRead*, upon
receiving one of participant_1's participant discovery messages.
Note, with DPSE_, there is no runtime check of QoS consistency between
*DataWriters* and *DataReaders*, because no endpoint discovery messages
are exchanged. This makes it extremely important that users of DPSE_
ensure that the QoS set for a local *DataWriter* and *DataReader* is the
same QoS being used by another *DomainParticipant* to assert it as a
remote *DataWriter* or *DataReader*.
Asymmetric Matching and Lost Samples
------------------------------------
The DDS discovery process is necessary to establish communication between
a *DataWriter* and a *DataReader*. However, it is important to understand that DDS
applications do not connect to each other; there is no handshake protocol to
ensure that a *DataReader* is ready to receive data from a *DataWriter*. Thus,
it is possible that a *DataWriter* matches a *DataReader* before the *DataReader*
matches the *DataWriter* (and vice versa). For this reason, it is possible that
data published by a *DataWriter* is not received by the *DataReader*, even on a
local network.
The reason for this asymmetric behavior can be for any number of reasons,
such as, but not limited to:
- Network delays
- Packets taking different paths through the network
- Address resolution delays
- OS scheduling
DDS offers some solutions to mitigate this problem, e.g., the DURABILITY QoS
policy, but in other cases it may be necessary for applications to implement
their own synchronization protocols.