4.8. Discovery¶
This section discusses the implementation of discovery plugins in RTI Connext Micro. For a general overview of discovery in RTI Connext Micro, see What is Discovery?.
Connext Micro discovery traffic is conducted through transports. Please see the Transports section for more information about registering and configuring transports.
4.8.1. What is Discovery?¶
Discovery is the behind-the-scenes way in which RTI Connext Micro 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 Connext Micro 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 Connext Micro on the sending and receiving sides.
Note that this chapter is shared between Connext Micro and Connext Cert. However Connext Cert only supports static endpoint discovery described in Static Discovery Plugin.
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 the Discovery chapter in the RTI Connext DDS Core Libraries User’s Manual (available here if you have Internet access).
4.8.1.1. 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, Connext Micro 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 Connext Micro (you may be able to change it in a future version).
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.
4.8.1.2. 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, Connext 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 the section on Discovery Implementation in the RTI Connext DDS Core Libraries User’s Manual for more details about the discovery process (available here if you have Internet access).
4.8.2. Configuring Participant Discovery Peers¶
An RTI Connext Micro 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.
4.8.2.1. 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:
< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
which means a literal.
ADDRESS = <PREFIX://><ADDRESS> |
@<PREFIX://><ADDRESS> |
INDEX@<PREFIX>://<ADDRESS>
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:
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");
4.8.3. Configuring Initial Peers and Adding 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.
4.8.4. 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:
< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
which means a literal.
ADDRESS = <PREFIX://><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:
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:
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:
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:
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");
4.8.5. 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:
< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
which means a literal.
ADDRESS = <PREFIX://><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:
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:
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:
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:
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");
4.8.6. 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.
4.8.7. 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 Connext Micro provides two ways of determinig endpoint information of other DomainParticipants: Dynamic Discovery Plugin and Static Discovery Plugin.
4.8.7.1. Dynamic 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.
4.8.7.2. 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.
Please refer to the C API Reference and C++ API Reference for the following remote entity assertion APIs:
4.8.7.2.1. 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.
/* 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;
}
4.8.7.2.2. 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:
/* 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:
/* 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.
4.8.8. 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.