Discovery#

Introduction#

What you’ll learn

In this module, you’ll learn how discovery works in Connext. Discovery is the process where DomainParticipants find out about each other.

You will do the following:

  • Understand the basics of how discovery works

  • Configure your applications to run on different machines and networks

  • Access discovery information within your application

In most modules in this guide, we ran all applications on the same machine to keep things simple. This module demonstrates how to run your applications on different machines and networks: from a LAN, a WAN, and a cloud environment.

How to complete this module#

To complete this module you’ll need the following:

  • 20-30 minutes

  • A Connext installation (full installation or pip-based installation). See Get Started.

  • A text editor or IDE to write your code.

In this module, we’ll update the applications created in the Publish-Subscribe module. Completing that module is recommended but not required. The code is available in RTI’s GitHub examples repository.

Cloning the GitHub repository

Clone the GitHub repository with the following command:

$ git clone --recurse-submodule https://github.com/rticommunity/rticonnextdds-examples.git

The code for this module is located in the tutorials/discovery directory. See the README.md files for additional instructions.

1. Understand the basics of discovery#

When you write a Connext application, the first entity you usually create is a DomainParticipant. Discovery is an ongoing process that starts, by default, as soon as you create a DomainParticipant.

When you create a DomainParticipant, you specify a domain ID. A domain is a logical network where DomainParticipants can communicate.

participant = dds.DomainParticipant(domainId: 0)

During the discovery process, DomainParticipants:

  • Find out about other DomainParticipants in the same domain.

  • Exchange information about their DataWriters and DataReaders.

  • Determine if they have matching Topics, and compatible data types and QoS with their own DataReaders and DataWriters.

If a DataWriter and DataReader are determined to be compatible, they communicate.

A Remote Procedure Call (RPC) client and service are each composed of a DataWriter and a DataReader, and their discovery process is similar.

The next sections demonstrate how to configure discovery.

2. Run your applications on a LAN#

With multicast support#

By default, Connext uses shared memory and UDP multicast to discover other DomainParticipants on the same domain. When you run your applications on the same machine, or on a LAN that supports multicast, the DomainParticipants communicate automatically; no configuration is required.

If you have access to two machines in the same multicast-enabled LAN, run the applications from tutorials/discovery as follows:

Machine 1 - Run a sensor publisher application.

python home_automation_publisher.py

Machine 2 - Run the subscriber application.

python home_automation_subscriber.py

First, build the applications on each machine following the instructions in tutorials/discovery/c++11/README.md.

Machine 1 - Run a sensor publisher application.

cd build
./home_automation_publisher

Machine 2 - Run the subscriber application.

cd build
./home_automation_subscriber

Without multicast support#

When your LAN doesn’t support multicast, DomainParticipants can communicate using initial peers. A peer is a participant that can communicate with other participants in the same domain. The initial peers list will include the addresses your DomainParticipants should contact.

You can configure the initial peers using the Discovery QoS policy or the environment variable NDDS_DISCOVERY_PEERS.

For each application, create or edit the USER_QOS_PROFILES.xml file to include the following <discovery> tag:

USER_QOS_PROFILES.xml#
<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="https://community.rti.com/schema/current/rti_dds_profiles.xsd">
    <qos_library name="MyLibrary">
            <qos_profile name="MyProfile" is_default_qos="true">
            <!-- ... -->
            <domain_participant_qos>
                <discovery>
                    <initial_peers>
                        <!-- use the address of Machine 1 -->
                        <element>192.168.1.10</element>

                        <!-- use the address of Machine 2 -->
                        <element>192.168.1.11</element>
                    </initial_peers>
                </discovery>
            </domain_participant_qos>
            </qos_profile>
    </qos_library>
</dds>

The <domain_participant_qos> tag configures the DomainParticipant in the applications.

Make sure the USER_QOS_PROFILES.xml file is located in the same directory where you run each application.

On each machine running an application, set the environment variable NDDS_DISCOVERY_PEERS to the addresses of the other machines. For example:

On Machine 1 (192.168.1.10):

export NDDS_DISCOVERY_PEERS=192.168.1.11

On Machine 2 (192.168.1.11):

export NDDS_DISCOVERY_PEERS=192.168.1.10

Replace the IP addresses/host names with the actual addresses/host names of the machines running your applications. Then run each application as noted above.

3. Run your applications across a WAN or in the Cloud#

To simplify communication between separate LANs across a WAN, or in the Cloud, Connext includes Real-Time WAN Transport. This transport plugin provides two main benefits:

  • It traverses NATs automatically

  • It can be configured to map all traffic to a single physical port

These features simplify network configuration and allow running applications behind load balancers transparently.

This step assumes there is a public participant that is reachable via a public IP address and port (for example, 50.10.23.45:2345), and an internal participant that does not expose a public address. The public participant may be directly accessible at that address, or it may be behind a NAT with a static mapping to a private address.

In this example, configure the subscriber application’s participant as the public participant to expose the public address 50.10.23.45:2345 as follows:

USER_QOS_PROFILES.xml (Machine 1)#
<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="https://community.rti.com/schema/current/rti_dds_profiles.xsd">
   <qos_library name="MyLibrary">
         <qos_profile name="ExternalParticipant" is_default_qos="true">
            <base_name>
               <element>BuiltinQosSnippetLib::Transport.UDP.WAN</element>
            </base_name>

            <domain_participant_qos>
               <transport_builtin>
                  <udpv4_wan>
                     <public_address>50.10.23.45</public_address>
                     <comm_ports>
                           <default>
                              <host>2345</host>
                           </default>
                     </comm_ports>
                  </udpv4_wan>
               </transport_builtin>
            </domain_participant_qos>
         </qos_profile>
   </qos_library>
</dds>

Next, configure the publisher application’s participant as the internal participant, as shown below, to contact the public address.

USER_QOS_PROFILES.xml (Machine 2)#
<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="https://community.rti.com/schema/current/rti_dds_profiles.xsd">
   <qos_library name="MyLibrary">
         <qos_profile name="InternalParticipant" is_default_qos="true">
            <base_name>
               <element>BuiltinQosSnippetLib::Transport.UDP.WAN</element>
            </base_name>

            <domain_participant_qos>
               <discovery>
                  <initial_peers>
                     <element>0@udpv4_wan://50.10.23.45:2345</element>
                  </initial_peers>
               </discovery>
            </domain_participant_qos>
         </qos_profile>
   </qos_library>
</dds>

Now run the subscriber and publisher applications as noted in step 2, ensuring each application’s corresponding USER_QOS_PROFILES.xml file is located in the same directory where you run each application.

Cloud Discovery Service#

As your system grows, managing the configuration of the addresses for each participant can get complex, especially if multicast is not available. Cloud Discovery Service acts as a reliable “rendezvous” point for Connext applications. Your participants only need to configure the address of the Cloud Discovery Service as their initial peer. The service then transparently provides the addresses of the other participants.

Cloud Discovery Service facilitates the initial discovery process; after that, communication between applications is fully peer-to-peer.

4. Monitor matching publications or subscriptions#

All discovery information is available to applications. In this exercise, you’ll update the home automation subscriber application to do the following:

  • Monitor the Publication Matched status to be notified when a matching DataWriter joins or leaves.

  • Look up information about the current DataWriters.

A publisher and a subscriber match when they run on the same domain and use the same Topic name, compatible types, and QoS.

Open home_automation_subscriber.py from tutorials/publish_subscribe and create a listener to monitor the Subscription Matched status using the code below.

class SensorListener(dds.NoOpDataReaderListener):
    def on_subscription_matched(self, reader, matched_status):
        print(
                f"Total publishers: {matched_status.current_count}, "
                + f"Change: {matched_status.current_count_change}"
        )

In the same file, attach the listener to the reader as follows:

reader.set_listener(SensorListener(), dds.StatusMask.SUBSCRIPTION_MATCHED)

This setting will print a message every time a new publisher matches or stops communicating with your subscriber.

Next, add the code below to obtain and print some information about the matching publishers.

class SensorListener(dds.NoOpDataReaderListener):
    def on_subscription_matched(self, reader, matched_status):
        # ...
        for i, publication in enumerate(reader.matched_publications):
            pub_data = reader.matched_publication_data(publication)
            print(f"Publisher {i}:")
            print(f"    Publisher Virtual GUID: {pub_data.virtual_guid}")

You can review a completed version of home_automation_subscriber.py in RTI’s GitHub examples repository.

Open home_automation_subscriber.cxx from tutorials/publish_subscribe and create a listener to monitor the Subscription Matched status:

class SensorListener
        : public dds::sub::NoOpDataReaderListener<DeviceStatus> {

    virtual void on_subscription_matched(
            dds::sub::DataReader<DeviceStatus> &reader,
            const dds::core::status::SubscriptionMatchedStatus &status)
    {
        std::cout << std::endl << "Total publishers: " << status.current_count()
                << ", Change: " << status.current_count_change()
                << std::endl;

    }
};

In the same file, attach the listener to the reader as follows:

std::shared_ptr<SensorListener> sensor_listener =
        std::make_shared<SensorListener>();
reader.set_listener(
        sensor_listener,
        dds::core::status::StatusMask::subscription_matched());

This setting will print a message every time a new publisher matches or stops communicating with your subscriber.

Next, add the code below to obtain and print some information about the matching publishers:

class SensorListener
        : public dds::sub::NoOpDataReaderListener<DeviceStatus> {

    virtual void on_subscription_matched(
            dds::sub::DataReader<DeviceStatus> &reader,
            const dds::core::status::SubscriptionMatchedStatus &status)
    {
        // ...

        dds::core::InstanceHandleSeq publications =
                dds::sub::matched_publications(reader);

        for (int i = 0; i < publications.size(); i++) {
            dds::topic::PublicationBuiltinTopicData pub_data =
                    dds::sub::matched_publication_data(reader, publications[i]);

            std::cout << "Publisher " << i << ": " << std::endl;
            std::cout << "    Publisher Virtual GUID: "
                    << pub_data.extensions().virtual_guid() << std::endl;

        }
    }
};

You can review a completed version of home_automation_subscriber.cxx in RTI’s GitHub examples repository.

To run your applications, see the instructions in the Publish-Subscribe module or in the README.md files under tutorials/discovery on GitHub.

You can expand on this example with the following optional exercises.

Identify your publishers with a name (optional)

To help identify your publishers (or subscribers), you can give them a name. In this example, each publisher is a different room, so you’ll use that information in naming them.

Add the following code to home_automation_publisher.py:

writer_qos = participant.default_datawriter_qos
writer_qos.entity_name.name = room_name
writer = dds.DataWriter(topic, writer_qos)

See the completed home_automation_publisher.py file on GitHub.

Next, print that name in home_automation_subscriber.py:

class SensorListener(dds.NoOpDataReaderListener):
    def on_subscription_matched(self, reader, matched_status):
        #...
        for i, publication in enumerate(reader.matched_publications):
            pub_data = reader.matched_publication_data(publication)
            print(f"Publisher {i}:")
            # ...
            print(f"    Publisher Name: {pub_data.publication_name.name}")

See the completed home_automation_subscriber.py file on GitHub.

Re-run the publishers and subscribers. You should see the publisher names:

Total publishers: 1, Change: 1
Publisher 0:
    Publisher Virtual GUID: 01018e20ef4ff65b3cdf808a80000002
    Publisher Name: LivingRoom

Add the following code to home_automation_publisher.cxx:

dds::pub::qos::DataWriterQos qos =
        participant.extensions().default_datawriter_qos();
qos << rti::core::policy::EntityName(room_name);
dds::pub::DataWriter<DeviceStatus> writer(topic, qos);

See the completed home_automation_publisher.cxx file on GitHub.

Next, print that name in home_automation_subscriber.cxx:

class SensorListener
        : public dds::sub::NoOpDataReaderListener<DeviceStatus> {

    virtual void on_subscription_matched(
            dds::sub::DataReader<DeviceStatus> &reader,
            const dds::core::status::SubscriptionMatchedStatus &status)
    {
        // ...

        dds::core::InstanceHandleSeq publications =
                dds::sub::matched_publications(reader);

        for (int i = 0; i < publications.size(); i++) {
            dds::topic::PublicationBuiltinTopicData pub_data =
                    dds::sub::matched_publication_data(reader, publications[i]);

            std::cout << "Publisher " << i << ": " << std::endl;
            // ...
            std::cout << "    Publisher Name: "
                      << (pub_data.extensions().publication_name().name()
                            ? pub_data->publication_name().name().value()
                            : "") << std::endl;

        }
    }
};

See the completed home_automation_subscriber.cxx file on GitHub.

Compile and re-run the publishers and subscribers. You should see the publisher names:

Total publishers: 1, Change: 1
Publisher 0:
    Publisher Virtual GUID: 01018e20ef4ff65b3cdf808a80000002
    Publisher Name: LivingRoom
Monitor other status updates (optional)

Your applications can monitor other status updates such as OfferedIncompatibleQosStatus, which notifies a DataReader or a DataWriter that they’ve discovered another entity with incompatible QoS, preventing communication.

You can create a skeleton application with rtiddsgen that monitors all statuses by adding the options -create typefiles -create examplefiles -exampleTemplate advanced to the command line. For example:

$ rtiddsgen -language <language> -platform <platform> \
-create typefiles -create examplefiles -exampleTemplate advanced \
home_automation.idl
Choosing your language and platform

The platform name depends on the language and the package you installed. The following table shows some platform names you can use:

-language

-platform

c++11, c++98, java, c

  • Linux: x64Linux4gcc7.3.0, armv8Linux4gcc7.3.0, …

  • Windows: x64Win64VS2017, …

  • macOS: x64Darwin20clang12.0, arm64Darwin20clang12.0

and more…

c#

net5, net6, net8, …

python

universal

For example, to generate code for C++11 on Linux, you would use:

$ <install dir>/bin/rtiddsgen -language c++11 -platform x64Linux4gcc7.3.0 <file>.idl

Run rtiddsgen -help for all available options.

After running this command with your language and platform, inspect the generated publisher and subscriber example applications. For each status, you will see a callback function. For example:

def on_offered_incompatible_qos(
   self, writer: dds.DataWriter, status: dds.OfferedIncompatibleQosStatus
):
   print("Offered incompatible QoS")
void on_offered_incompatible_qos(
   dds::pub::DataWriter<DeviceStatus>& writer,
   const dds::core::status::OfferedIncompatibleQosStatus& status)
   override
{
}

Learn more#

This module introduced the basics of how the discovery process works. Starting when a DomainParticipant is created, we saw how Connext automatically discovers applications running on the same machine or on a LAN with multicast. We also reviewed how to configure discovery in LANs without multicast and across WANs. Finally, a code exercise showed a way to access discovery information in an application.

Next Steps

Related modules:

  • Partitioning. This module shows how to partition applications and data to scale your system. This includes preventing some applications from discovering each other.

  • Debugging. This module helps you debug your applications when they don’t discover each other or communicate.

  • Docker & Kubernetes. This module provides resources to deploy your applications in the Cloud.

More about the discovery process:

More about Real-Time WAN Transport and Cloud Discovery Service:

Additional code examples:

  • Built-in Discovery Topics - Connext exposes discovery events and information as several built-in Topics that your applications can subscribe to.

  • Property QoS - The Property QoS allows attaching any key/value discovery metadata to publishers, subscribers and participants, in a way similar to the publication name we used in the optional exercise.

  • Discovery Snapshot - Discovery snapshot is a debugging utility to easily display discovery information.

Was this page helpful?

Back to Learn