Hello World! in more detail¶
The previous chapter focused on building the Hello World! example while this chapter will focus on the code itself; what has to be done to code this small example.
Hello World! DataType¶
Data-Centric Architecture¶
By creating a Data-centric architecture, you get a loosely coupled information-driven system. It emphasizes a data layer that is common for all distributed applications within the system. Because there is no direct coupling among the applications in the DDS model, they can be added and removed easily in a modular and scalable manner. This makes that the complexity of a data-centric architecture doesn’t really increase when more and more publishers/subscribers are added.
The Hello World! example has a very simple ‘data layer’ of only
one data type HelloWorldData_Msg
(please read on).
The subscriber and publisher are not aware of each other.
The former just waits until somebody provides the data it
requires, while the latter just publishes the data without
considering the number of interested parties. In other words,
it doesn’t matter for the publisher if there are none or
multiple subscribers (try running the Hello World! example
by starting multiple HelloworldSubscribers before starting a
HelloworldPublisher). A publisher just writes the data. The
DDS middleware takes care of delivering the data when needed.
HelloWorldData.idl¶
To be able to sent data from a writer to a reader, DDS needs to know the data type. For the Hello World! example, this data type is described using IDL and is located in HelloWorldData.idl. This IDL file will be compiled by a IDL compiler which in turn generates a C language source and header file. These generated source and header file will be used by the HelloworldSubscriber and HelloworldPublisher in order to communicate the Hello World! message between the HelloworldPublisher and the HelloworldSubscriber.
Hello World! IDL¶
There are a few ways to describe the structures that make up the data layer. The HelloWorld uses the IDL language to describe the data type in HelloWorldData.idl:
1module HelloWorldData
2{
3 struct Msg
4 {
5 long userID;
6 string message;
7 };
8 #pragma keylist Msg userID
9};
An extensive explanation of IDL lies outside the scope of this example. Nevertheless, a quick overview of this example is given anyway.
First, there’s the module HelloWorldData
. This is a kind
of namespace or scope or similar.
Within that module, there’s the struct Msg
. This is the
actual data structure that is used for the communication. In
this case, it contains a userID
and message
.
The combination of this module and struct translates to the following when using the c language.
typedef struct HelloWorldData_Msg
{
int32_t userID;
char * message;
} HelloWorldData_Msg;
When it is translated to a different language, it will look different and more tailored towards that language. This is the advantage of using a data oriented language, like IDL, to describe the data layer. It can be translated into different languages after which the resulting applications can communicate without concerns about the (possible different) programming languages these application are written in.
Generate Sources and Headers¶
Like already mentioned in the Hello World! IDL chapter, an IDL file contains the description of data type(s). This needs to be translated into programming languages to be useful in the creation of DDS applications.
To be able to do that, there’s a pre-compile step that actually compiles the IDL file into the desired programming language.
A java application org.eclipse.cyclonedds.compilers.Idlc
is supplied to support this pre-compile step. This is available
in idlc-jar-with-dependencies.jar
The compilation from IDL into c source code is as simple as starting that java application with an IDL file. In the case of the Hello World! example, that IDL file is HelloWorldData.idl.
java -classpath "<install_dir>/share/CycloneDDS/idlc/idlc-jar-with-dependencies.jar" org.eclipse.cyclonedds.compilers.Idlc HelloWorldData.idl
- Windows
The
HelloWorldType
project within the HelloWorld solution.- Linux
The
make datatype
command.
This will result in new generated/HelloWorldData.c
and
generated/HelloWorldData.h
files that can be used in the Hello World! publisher and
subscriber applications.
The application has to be rebuild when the data type source files were re-generated.
Again, this is all for the native builds. When using CMake, all this is done automatically.
HelloWorldData.c & HelloWorldData.h¶
As described in the Hello World! DataType paragraph, the IDL compiler will generate this source and header file. These files contain the data type of the messages that are sent and received.
While the c source has no interest for the application developers, HelloWorldData.h contains some information that they depend on. For example, it contains the actual message structure that is used when writing or reading data.
typedef struct HelloWorldData_Msg
{
int32_t userID;
char * message;
} HelloWorldData_Msg;
It also contains convenience macros to allocate and free memory space for the specific data types.
HelloWorldData_Msg__alloc()
HelloWorldData_Msg_free(d,o)
It contains an extern variable that describes the data type to the DDS middleware as well.
HelloWorldData_Msg_desc
Hello World! Business Logic¶
Apart from the HelloWorldData data type files that the Hello World! example uses to send messages, the Hello World! example also contains two (user) source files (subscriber.c and publisher.c), containing the business logic.
Hello World! Subscriber Source Code¶
Subscriber.c contains the source that will wait for a Hello World! message and reads it when it receives one.
1#include "dds/dds.h"
2#include "HelloWorldData.h"
3#include <stdio.h>
4#include <string.h>
5#include <stdlib.h>
6
7/* An array of one message (aka sample in dds terms) will be used. */
8#define MAX_SAMPLES 1
9
10int main (int argc, char ** argv)
11{
12 dds_entity_t participant;
13 dds_entity_t topic;
14 dds_entity_t reader;
15 HelloWorldData_Msg *msg;
16 void *samples[MAX_SAMPLES];
17 dds_sample_info_t infos[MAX_SAMPLES];
18 dds_return_t rc;
19 dds_qos_t *qos;
20 (void)argc;
21 (void)argv;
22
23 /* Create a Participant. */
24 participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL);
25 if (participant < 0)
26 DDS_FATAL("dds_create_participant: %s\n", dds_strretcode(-participant));
27
28 /* Create a Topic. */
29 topic = dds_create_topic (
30 participant, &HelloWorldData_Msg_desc, "HelloWorldData_Msg", NULL, NULL);
31 if (topic < 0)
32 DDS_FATAL("dds_create_topic: %s\n", dds_strretcode(-topic));
33
34 /* Create a reliable Reader. */
35 qos = dds_create_qos ();
36 dds_qset_reliability (qos, DDS_RELIABILITY_RELIABLE, DDS_SECS (10));
37 reader = dds_create_reader (participant, topic, qos, NULL);
38 if (reader < 0)
39 DDS_FATAL("dds_create_reader: %s\n", dds_strretcode(-reader));
40 dds_delete_qos(qos);
41
42 printf ("\n=== [Subscriber] Waiting for a sample ...\n");
43 fflush (stdout);
44
45 /* Initialize sample buffer, by pointing the void pointer within
46 * the buffer array to a valid sample memory location. */
47 samples[0] = HelloWorldData_Msg__alloc ();
48
49 /* Poll until data has been read. */
50 while (true)
51 {
52 /* Do the actual read.
53 * The return value contains the number of read samples. */
54 rc = dds_read (reader, samples, infos, MAX_SAMPLES, MAX_SAMPLES);
55 if (rc < 0)
56 DDS_FATAL("dds_read: %s\n", dds_strretcode(-rc));
57
58 /* Check if we read some data and it is valid. */
59 if ((rc > 0) && (infos[0].valid_data))
60 {
61 /* Print Message. */
62 msg = (HelloWorldData_Msg*) samples[0];
63 printf ("=== [Subscriber] Received : ");
64 printf ("Message (%"PRId32", %s)\n", msg->userID, msg->message);
65 fflush (stdout);
66 break;
67 }
68 else
69 {
70 /* Polling sleep. */
71 dds_sleepfor (DDS_MSECS (20));
72 }
73 }
74
75 /* Free the data location. */
76 HelloWorldData_Msg_free (samples[0], DDS_FREE_ALL);
77
78 /* Deleting the participant will delete all its children recursively as well. */
79 rc = dds_delete (participant);
80 if (rc != DDS_RETCODE_OK)
81 DDS_FATAL("dds_delete: %s\n", dds_strretcode(-rc));
82
83 return EXIT_SUCCESS;
84}
We will be using the DDS API and the HelloWorldData_Msg type to receive data. For that, we need to include the appropriate header files.
#include "dds/dds.h"
#include "HelloWorldData.h"
The main starts with defining a few variables that will be used for reading the Hello World! message. The entities are needed to create a reader.
dds_entity_t participant;
dds_entity_t topic;
dds_entity_t reader;
Then there are some buffers that are needed to actually read the data.
HelloWorldData_Msg *msg;
void *samples[MAX_SAMPLES];
dds_sample_info_t info[MAX_SAMPLES];
To be able to create a reader, we first need a participant. This participant is part of a specific communication domain. In the Hello World! example case, it is part of the default domain.
participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL);
The another requisite is the topic which basically describes the data type that is used by the reader. When creating the topic, the data description for the DDS middleware that is present in the HelloWorldData.h is used. The topic also has a name. Topics with the same data type description, but with different names, are considered different topics. This means that readers/writers created with a topic named “A” will not interfere with readers/writers created with a topic named “B”.
topic = dds_create_topic (participant, &HelloWorldData_Msg_desc,
"HelloWorldData_Msg", NULL, NULL);
When we have a participant and a topic, we then can create the reader. Since the order in which the Hello World! Publisher and Hello World! Subscriber are started shouldn’t matter, we need to create a so called ‘reliable’ reader. Without going into details, the reader will be created like this
dds_qos_t *qos = dds_create_qos ();
dds_qset_reliability (qos, DDS_RELIABILITY_RELIABLE, DDS_SECS (10));
reader = dds_create_reader (participant, topic, qos, NULL);
dds_delete_qos(qos);
We are almost able to read data. However, the read expects an
array of pointers to valid memory locations. This means the
samples array needs initialization. In this example, we have
an array of only one element: #define MAX_SAMPLES 1
.
So, we only need to initialize one element.
samples[0] = HelloWorldData_Msg__alloc ();
Now everything is ready for reading data. But we don’t know if there is any data. To simplify things, we enter a polling loop that will exit when data has been read.
Within the polling loop, we do the actual read. We provide the
initialized array of pointers (samples
), an array that
holds information about the read sample(s) (info
), the
size of the arrays and the maximum number of samples to read.
Every read sample in the samples array has related information
in the info array at the same index.
ret = dds_read (reader, samples, info, MAX_SAMPLES, MAX_SAMPLES);
The dds_read
function returns the number of samples it
actually read. We can use that to determine if the function actually
read some data. When it has, then it is still possible that the
data part of the sample is not valid. This has some use cases
when there is no real data, but still the state of the related
sample has changed (for instance it was deleted). This will
normally not happen in the Hello World! example. But we check
for it anyway.
if ((ret > 0) && (info[0].valid_data))
If data has been read, then we can cast the void pointer to the actual message data type and display the contents. The polling loop is quit as well in this case.
msg = (HelloWorldData_Msg*) samples[0];
printf ("=== [Subscriber] Received : ");
printf ("Message (%d, %s)\n", msg->userID, msg->message);
break;
When data is received and the polling loop is stopped, we need to clean up.
HelloWorldData_Msg_free (samples[0], DDS_FREE_ALL);
dds_delete (participant);
All the entities that are created using the participant are also deleted. This means that deleting the participant will automatically delete the topic and reader as well.
Hello World! Publisher Source Code¶
Publisher.c contains the source that will write an Hello World! message on which the subscriber is waiting.
1#include "dds/dds.h"
2#include "HelloWorldData.h"
3#include <stdio.h>
4#include <stdlib.h>
5
6int main (int argc, char ** argv)
7{
8 dds_entity_t participant;
9 dds_entity_t topic;
10 dds_entity_t writer;
11 dds_return_t rc;
12 HelloWorldData_Msg msg;
13 uint32_t status = 0;
14 (void)argc;
15 (void)argv;
16
17 /* Create a Participant. */
18 participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL);
19 if (participant < 0)
20 DDS_FATAL("dds_create_participant: %s\n", dds_strretcode(-participant));
21
22 /* Create a Topic. */
23 topic = dds_create_topic (
24 participant, &HelloWorldData_Msg_desc, "HelloWorldData_Msg", NULL, NULL);
25 if (topic < 0)
26 DDS_FATAL("dds_create_topic: %s\n", dds_strretcode(-topic));
27
28 /* Create a Writer. */
29 writer = dds_create_writer (participant, topic, NULL, NULL);
30 if (writer < 0)
31 DDS_FATAL("dds_create_writer: %s\n", dds_strretcode(-writer));
32
33 printf("=== [Publisher] Waiting for a reader to be discovered ...\n");
34 fflush (stdout);
35
36 rc = dds_set_status_mask(writer, DDS_PUBLICATION_MATCHED_STATUS);
37 if (rc != DDS_RETCODE_OK)
38 DDS_FATAL("dds_set_status_mask: %s\n", dds_strretcode(-rc));
39
40 while(!(status & DDS_PUBLICATION_MATCHED_STATUS))
41 {
42 rc = dds_get_status_changes (writer, &status);
43 if (rc != DDS_RETCODE_OK)
44 DDS_FATAL("dds_get_status_changes: %s\n", dds_strretcode(-rc));
45
46 /* Polling sleep. */
47 dds_sleepfor (DDS_MSECS (20));
48 }
49
50 /* Create a message to write. */
51 msg.userID = 1;
52 msg.message = "Hello World";
53
54 printf ("=== [Publisher] Writing : ");
55 printf ("Message (%"PRId32", %s)\n", msg.userID, msg.message);
56 fflush (stdout);
57
58 rc = dds_write (writer, &msg);
59 if (rc != DDS_RETCODE_OK)
60 DDS_FATAL("dds_write: %s\n", dds_strretcode(-rc));
61
62 /* Deleting the participant will delete all its children recursively as well. */
63 rc = dds_delete (participant);
64 if (rc != DDS_RETCODE_OK)
65 DDS_FATAL("dds_delete: %s\n", dds_strretcode(-rc));
66
67 return EXIT_SUCCESS;
68}
We will be using the DDS API and the HelloWorldData_Msg type to sent data. For that, we need to include the appropriate header files.
#include "dds/dds.h"
#include "HelloWorldData.h"
Just like with the reader in subscriber.c, we need a participant and a topic to be able to create a writer. We use the same topic name as in subscriber.c. Otherwise the reader and writer are not considered related and data will not be sent between them.
dds_entity_t participant;
dds_entity_t topic;
dds_entity_t writer;
participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL);
topic = dds_create_topic (participant, &HelloWorldData_Msg_desc,
"HelloWorldData_Msg", NULL, NULL);
writer = dds_create_writer (participant, topic, NULL, NULL);
The DDS middleware is a publication/subscription implementation. This means that it will discover related readers and writers (i.e. readers and writers sharing the same data type and topic name) and connect them so that written data can be received by readers without the application having to worry about it. There is a catch though: this discovery and coupling takes a small amount of time. There are various ways to work around this problem. The following can be done to properly connect readers and writers:
Wait for the publication/subscription matched events
The Subscriber should wait for a subscription matched event
The Publisher should wait for a publication matched event.
The use of these events will be outside the scope of this example
Poll for the publication/subscription matches statuses
The Subscriber should poll for a subscription matched status to be set
The Publisher should poll for a publication matched status to be set
The Publisher in this example uses the polling schema.
Let the publisher sleep for a second before writing a sample. This is not recommended since a second may not be enough on several networks
Accept that the reader miss a few samples at startup. This may be acceptable in cases where the publishing rate is high enough.
As said, the publisher of this example polls for the publication matched status. To make this happen, the writer must be instructed to ‘listen’ for this status. The following line of code makes sure the writer does so.
dds_set_status_mask(writer, DDS_PUBLICATION_MATCHED_STATUS);
Now the polling may start:
while(true)
{
uint32_t status;
ret = dds_get_status_changes (writer, &status);
DDS_ERR_CHECK(ret, DDS_CHECK_REPORT | DDS_CHECK_EXIT);
if (status == DDS_PUBLICATION_MATCHED_STATUS) {
break;
}
/* Polling sleep. */
dds_sleepfor (DDS_MSECS (20));
}
After this loop, we are sure that a matching reader has been started. Now, we commence to writing the data. First the data must be initialized
HelloWorldData_Msg msg;
msg.userID = 1;
msg.message = "Hello World";
Then we can actually sent the message to be received by the subscriber.
ret = dds_write (writer, &msg);
After the sample is written, we need to clean up.
ret = dds_delete (participant);
All the entities that are created using the participant are also deleted. This means that deleting the participant will automatically delete the topic and writer as well.