user-header🔗
Introduction🔗
This example demonstrates how to leverage the user-header to send custom meta-information with each sample. Specifically, we want to send a timestamp alongside the sample. The example contains code for the typed and untyped C++ API as well as for the C language binding.
Expected Output🔗
Code walkthrough🔗
The examples uses the user-header and user-payload which is defined in user_header_and_payload_types.hpp
for the C++ API and in user_header_and_payload_types.h
for the C API. The user-header consists of a simple uint64_t
which is used to transmit a timestamp and the user-payload is a Fibonacci number.
This are the definitions for the C++ API:
struct Header
{
uint64_t publisherTimestamp{0};
};
struct Data
{
uint64_t fibonacci{0};
};
This are the definitions for the C API:
typedef struct
{
uint64_t publisherTimestamp;
} Header;
typedef struct
{
uint64_t fibonacci;
} Data;
Publisher Typed C++ API🔗
At first, there are includes for the user-header and user-payload types and the iceoryx includes for publisher and runtime.
#include "user_header_and_payload_types.hpp"
#include "iceoryx_hoofs/posix_wrapper/signal_watcher.hpp"
#include "iceoryx_posh/popo/publisher.hpp"
#include "iceoryx_posh/runtime/posh_runtime.hpp"
Next, the iceoryx runtime is initialized. With this call, the application will be registered at RouDi
,
the routing and discovery daemon.
constexpr char APP_NAME[] = "iox-cpp-user-header-publisher";
iox::runtime::PoshRuntime::initRuntime(APP_NAME);
Now, we create the publisher. Unlike the other examples, this uses the second template parameter to define the user-header. This is the only change compared to the other examples with the typed C++ API.
iox::popo::Publisher<Data, Header> publisher({"Example", "User-Header", "Timestamp"});
In the main loop, a Fibonacci sequence is created and every second a number is published.
The Fibonacci number is passed to the loan
method which construct the Data
object if the loaning was successful.
This example uses the functional approach with and_then
and or_else
. Please have a look at the icehello
example
if you prefer a more traditional approach.
uint64_t timestamp = 42;
uint64_t fibonacciLast = 0;
uint64_t fibonacciCurrent = 1;
while (!iox::posix::hasTerminationRequested())
{
auto fibonacciNext = fibonacciCurrent + fibonacciLast;
fibonacciLast = fibonacciCurrent;
fibonacciCurrent = fibonacciNext;
publisher.loan(Data{fibonacciCurrent})
.and_then([&](auto& sample) {
// ...
})
.or_else([&](auto& error) {
// ...
});
constexpr uint64_t MILLISECONDS_SLEEP{1000U};
std::this_thread::sleep_for(std::chrono::milliseconds(MILLISECONDS_SLEEP));
timestamp += MILLISECONDS_SLEEP;
}
When the loaning was successful, we get a sample and can access it in the and_then
branch.
The sample has a getUserHeader
method which returns a reference to the user-header we specified with the template parameter.
In this case it's the Header
struct and we set the publisherTimestamp
.
sample.getUserHeader().publisherTimestamp = timestamp;
sample.publish();
std::cout << APP_NAME << " sent data: " << fibonacciCurrent << " with timestamp " << timestamp << "ms"
<< std::endl;
If the loaning fails, the or_else
branch is executed, which prints an error message
std::cout << APP_NAME << " could not loan sample! Error code: " << error << std::endl;
Publisher Untyped C++ API🔗
The example with the untyped C++ publisher is quite similar to the one with the typed publisher. The few differences will be discussed in this section.
At first there is a different include
#include "iceoryx_posh/popo/untyped_publisher.hpp"
When the publisher is created, there is also no notion of a user-header and it looks exactly the same like already shown in other examples.
iox::popo::UntypedPublisher publisher({"Example", "User-Header", "Timestamp"});
Variations come again into play when the chunk is loaned. Since the API is untyped,
the parameter for the user-header have to be provided with the loan
call. These are optional parameter and
are set to values indicating no user-header by default.
publisher.loan(sizeof(Data), alignof(Data), sizeof(Header), alignof(Header))
.and_then([&](auto& userPayload) {
// ...
})
.or_else([&](auto& error) {
// ...
});
Lastly, since the untyped C++ API returns a void
pointer to the user-payload, there is an intermediate step
to access the user-header by obtaining a ChunkHeader
first, which provides a method to get the user-header.
This also return a void
pointer, which makes a cast to the actual type necessary.
auto header = static_cast<Header*>(iox::mepoo::ChunkHeader::fromUserPayload(userPayload)->userHeader());
header->publisherTimestamp = timestamp;
auto data = static_cast<Data*>(userPayload);
data->fibonacci = fibonacciCurrent;
publisher.publish(userPayload);
std::cout << APP_NAME << " sent data: " << fibonacciCurrent << " with timestamp " << timestamp << "ms"
<< std::endl;
Publisher C API🔗
The example for the C API is similar to the one in icedelivery_in_c
and therefore only the user-header related parts are looked into.
The overall structure is the same like in the typed and untyped C++ API counterparts.
The functions to access the user-header are located in the following include
#include "iceoryx_binding_c/chunk.h"
Similar to the untyped C++ API, the user-header parameter are specified with the loan function. Since C does not have overloading, this is done by a different function
void* userPayload;
const uint32_t ALIGNMENT = 8;
enum iox_AllocationResult res = iox_pub_loan_aligned_chunk_with_user_header(
publisher, &userPayload, sizeof(Data), ALIGNMENT, sizeof(Header), ALIGNMENT);
Like with the untyped C++ API, the path to the user-header needs an intermediate step with the iox_chunk_header_t
and
explicit casting to since the functions return void
pointer.
iox_chunk_header_t* chunkHeader = iox_chunk_header_from_user_payload(userPayload);
Header* header = (Header*)iox_chunk_header_to_user_header(chunkHeader);
header->publisherTimestamp = timestamp;
Data* data = (Data*)userPayload;
data->fibonacci = fibonacciCurrent;
iox_pub_publish_chunk(publisher, userPayload);
// explicit cast to unsigned long since on macOS an uint64_t is a different built-in type than on Linux
printf("%s sent data: %lu with timestamp %ldms\n",
APP_NAME,
(unsigned long)fibonacciCurrent,
(unsigned long)timestamp);
fflush(stdout);
Subscriber Typed C++ API🔗
The boilerplate code for the subscriber is the same like for the publisher, therefore only the specific subscriber code is discussed in this section.
To use the subscriber, subscriber.hpp
needs to be included.
Similar to the publisher, the subscriber requires the same additional template parameter when
it is used with a user header.
iox::popo::Subscriber<Data, Header> subscriber({"Example", "User-Header", "Timestamp"});
The main loop is quite simple. The publisher is periodically polled and the data of the received sample is printed.
Again, the user-header is accessed by the getUserHeader
method of the sample.
while (!iox::posix::hasTerminationRequested())
{
subscriber.take().and_then([&](auto& sample) {
std::cout << APP_NAME << " got value: " << sample->fibonacci << " with timestamp "
<< sample.getUserHeader().publisherTimestamp << "ms" << std::endl;
});
constexpr std::chrono::milliseconds SLEEP_TIME{100U};
std::this_thread::sleep_for(SLEEP_TIME);
}
Subscriber Untyped C++ API🔗
On the subscriber side, the typed and untyped examples are even closer in similarity than the publisher example.
The notable difference is the take
method, which returns a void
pointer to the user-payload.
Like with the untyped publisher, in order to get the user-header the ChunkHeader
must be obtained.
Contrary to the untyped publisher, we must cast to a const T*
type, like const Header*
since we are not allowed to mutate the header from a subscriber.
At the end, the chunk must be released to prevent chunk leaks. This is done by calling the release
method with user-payload pointer.
subscriber.take().and_then([&](auto& userPayload) {
auto header =
static_cast<const Header*>(iox::mepoo::ChunkHeader::fromUserPayload(userPayload)->userHeader());
auto data = static_cast<const Data*>(userPayload);
std::cout << APP_NAME << " got value: " << data->fibonacci << " with timestamp "
<< header->publisherTimestamp << "ms" << std::endl;
subscriber.release(userPayload);
});
Subscriber C API🔗
Finally there is the C API example. Like with the publisher example for the C API we just take a look at the user-header specific parts. The overall structure is the same like in the typed and untyped C++ API counterparts.
As we already have seen with the untyped C++ example, the call to take the sample is independent of the usage of a user-header.
Likewise, the access to the user-header is done by the intermediate step of getting an iox_chunk_header_t
first.
Since we are not allowed to mutate the user-header from a subscriber, the respective functions with a _const
suffix must be used.
At last, the chunk is released in order to prevent chunk leaks.
const void* userPayload;
if (iox_sub_take_chunk(subscriber, &userPayload) == ChunkReceiveResult_SUCCESS)
{
const iox_chunk_header_t* chunkHeader = iox_chunk_header_from_user_payload_const(userPayload);
const Header* header = (const Header*)(iox_chunk_header_to_user_header_const(chunkHeader));
const Data* data = (const Data*)userPayload;
// explicit cast to unsigned long since on macOS an uint64_t is a different built-in type than on Linux
printf("%s got value: %lu with timestamp %ldms\n",
APP_NAME,
(unsigned long)data->fibonacci,
(unsigned long)header->publisherTimestamp);
fflush(stdout);
iox_sub_release_chunk(subscriber, userPayload);
}