|
| 1 | +# request_response |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +This example demonstrates how to use iceoryx in a client-server architecture |
| 6 | +using the request-response communication pattern. |
| 7 | +The main feature is that the server only response data at the request of a client. |
| 8 | + |
| 9 | +## Expected output |
| 10 | + |
| 11 | +[](https://asciinema.org/a/469913) |
| 12 | + |
| 13 | +## Code walkthrough |
| 14 | + |
| 15 | +### Client |
| 16 | + |
| 17 | +At first, the includes for the client port and request-response types and runtime are needed. |
| 18 | +<!-- [geoffrey] [geoffrey][iceoryx_examples/request_response/client_cxx_basic.cpp] [iceoryx includes] --> |
| 19 | +```cpp |
| 20 | +#include "request_and_response_types.hpp" |
| 21 | + |
| 22 | +#include "iceoryx_hoofs/posix_wrapper/signal_watcher.hpp" |
| 23 | +#include "iceoryx_posh/popo/client.hpp" |
| 24 | +#include "iceoryx_posh/runtime/posh_runtime.hpp" |
| 25 | +``` |
| 26 | + |
| 27 | +Next, the iceoryx runtime is initialized. With this call, |
| 28 | +the application will be registered at `RouDi`, the routing and discovery daemon. |
| 29 | +<!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic.cpp] [initialize runtime] --> |
| 30 | +```cpp |
| 31 | +constexpr char APP_NAME[] = "iox-cpp-request-response-client-basic"; |
| 32 | +iox::runtime::PoshRuntime::initRuntime(APP_NAME); |
| 33 | +``` |
| 34 | +
|
| 35 | +After creating the runtime, the client port is created based on a ServiceDescription. |
| 36 | +<!--[geoffrey][iceoryx_examples/request_response/client_cxx_basic.cpp][create client]--> |
| 37 | +```cpp |
| 38 | +iox::popo::Client<AddRequest, AddResponse> client({"Example", "Request-Response", "Add"}); |
| 39 | +``` |
| 40 | + |
| 41 | +The main goal of the client is to request from the server the sum of two numbers that the |
| 42 | +client sends. When to sum is received from the server, the received sum is re-used to insert |
| 43 | +it to the `addend` of the next request to send. |
| 44 | +This calculates a Fibonacci sequence. |
| 45 | +<!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic.cpp] [[send requests in a loop]] --> |
| 46 | +```cpp |
| 47 | + uint64_t fibonacciLast = 0; |
| 48 | + uint64_t fibonacciCurrent = 1; |
| 49 | + int64_t requestSequenceId = 0; |
| 50 | + int64_t expectedResponseSequenceId = requestSequenceId; |
| 51 | + while (!iox::posix::hasTerminationRequested()) |
| 52 | + { |
| 53 | + // send request to server for sum up two numbers |
| 54 | + |
| 55 | + // the server polls with an interval of 100ms |
| 56 | + constexpr std::chrono::milliseconds DELAY_TIME{150U}; |
| 57 | + std::this_thread::sleep_for(DELAY_TIME); |
| 58 | + |
| 59 | + // receive sum |
| 60 | + |
| 61 | + constexpr std::chrono::milliseconds SLEEP_TIME{950U}; |
| 62 | + std::this_thread::sleep_for(SLEEP_TIME); |
| 63 | + } |
| 64 | +``` |
| 65 | +
|
| 66 | +In the main loop, the client prepares a request using the `loan()` API. |
| 67 | +The request is a sample consisting of two numbers `augend` and `addend` that the server shall sum up. |
| 68 | +Additionally, the sample is marked with a sequence id that is incremented before |
| 69 | +every send cycle to ensure a correct ordering of the messages |
| 70 | +(`request.getRequestHeader().setSequenceId()`). |
| 71 | +The request is transmitted to the server via the `send()` API. |
| 72 | +<!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic.cpp] [[send request]] --> |
| 73 | +```cpp |
| 74 | + client.loan() |
| 75 | + .and_then([&](auto& request) { |
| 76 | + request.getRequestHeader().setSequenceId(requestSequenceId); |
| 77 | + expectedResponseSequenceId = requestSequenceId; |
| 78 | + requestSequenceId += 1; |
| 79 | + request->augend = fibonacciLast; |
| 80 | + request->addend = fibonacciCurrent; |
| 81 | + std::cout << APP_NAME << " Send Request: " << fibonacciLast << " + " << fibonacciCurrent << std::endl; |
| 82 | + request.send(); |
| 83 | + }) |
| 84 | + .or_else([](auto& error) { |
| 85 | + std::cout << "Could not allocate Request! Return value = " << static_cast<uint64_t>(error) << std::endl; |
| 86 | + }); |
| 87 | +``` |
| 88 | + |
| 89 | +In the same while loop the client receives the responses from the server using `take()` |
| 90 | +and extract the sequence id with `response.getResponseHeader().getSequenceId()`. |
| 91 | +When the server response comes in the correct order, the received sum is re-used to |
| 92 | +insert it to the `addend` of the next request to send. |
| 93 | + |
| 94 | +<!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic.cpp] [[take response]] --> |
| 95 | +```cpp |
| 96 | + while (client.take().and_then([&](const auto& response) { |
| 97 | + auto receivedSequenceId = response.getResponseHeader().getSequenceId(); |
| 98 | + if (receivedSequenceId == expectedResponseSequenceId) |
| 99 | + { |
| 100 | + fibonacciLast = fibonacciCurrent; |
| 101 | + fibonacciCurrent = response->sum; |
| 102 | + std::cout << APP_NAME << " Got Response : " << fibonacciCurrent << std::endl; |
| 103 | + } |
| 104 | + else |
| 105 | + { |
| 106 | + std::cout << "Got Response with outdated sequence ID! Expected = " << expectedResponseSequenceId |
| 107 | + << "; Actual = " << receivedSequenceId << "! -> skip" << std::endl; |
| 108 | + } |
| 109 | + })) |
| 110 | + { |
| 111 | + }; |
| 112 | +``` |
| 113 | +
|
| 114 | +### Server |
| 115 | +
|
| 116 | +At first, the includes for the server port and request-response types and runtime are needed. |
| 117 | +<!-- [geoffrey] [geoffrey][iceoryx_examples/request_response/server_cxx_basic.cpp] [iceoryx includes] --> |
| 118 | +```cpp |
| 119 | +#include "request_and_response_types.hpp" |
| 120 | +
|
| 121 | +#include "iceoryx_hoofs/posix_wrapper/signal_watcher.hpp" |
| 122 | +#include "iceoryx_posh/popo/server.hpp" |
| 123 | +#include "iceoryx_posh/runtime/posh_runtime.hpp" |
| 124 | +``` |
| 125 | + |
| 126 | +Next, the iceoryx runtime is initialized. With this call, |
| 127 | +the application will be registered at `RouDi`, the routing and discovery daemon. |
| 128 | +<!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_basic.cpp] [initialize runtime] --> |
| 129 | +```cpp |
| 130 | +constexpr char APP_NAME[] = "iox-cpp-user-header-publisher"; |
| 131 | +iox::runtime::PoshRuntime::initRuntime(APP_NAME); |
| 132 | +``` |
| 133 | +
|
| 134 | +After creating the runtime, the server port is created based on a ServiceDescription. |
| 135 | +<!--[geoffrey][iceoryx_examples/request_response/server_cxx_basic.cpp][create server]--> |
| 136 | +```cpp |
| 137 | +iox::popo::Server<AddRequest, AddResponse> server({"Example", "Request-Response", "Add"}); |
| 138 | +``` |
| 139 | + |
| 140 | +In the main loop, the server receives the requests from the client with |
| 141 | +`take()` and prepares with `loan()` a sample for the response. |
| 142 | +The received two numbers `augend` and `addend` are added and transmitted |
| 143 | +with the `send()` API. |
| 144 | +<!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_basic.cpp] [[process requests in a loop] [take request] [send response]] --> |
| 145 | +```cpp |
| 146 | + while (!iox::posix::hasTerminationRequested()) |
| 147 | + { |
| 148 | + server.take().and_then([&](const auto& request) { |
| 149 | + std::cout << APP_NAME << " Got Request: " << request->augend << " + " << request->addend << std::endl; |
| 150 | + |
| 151 | + server.loan(request) |
| 152 | + .and_then([&](auto& response) { |
| 153 | + response->sum = request->augend + request->addend; |
| 154 | + std::cout << APP_NAME << " Send Response: " << response->sum << std::endl; |
| 155 | + response.send(); |
| 156 | + }) |
| 157 | + .or_else([&](auto& error) { |
| 158 | + std::cout << APP_NAME |
| 159 | + << "Could not allocate Response! Return value = " << static_cast<uint64_t>(error) |
| 160 | + << std::endl; |
| 161 | + }); |
| 162 | + }); |
| 163 | + |
| 164 | + constexpr std::chrono::milliseconds SLEEP_TIME{100U}; |
| 165 | + std::this_thread::sleep_for(SLEEP_TIME); |
| 166 | + } |
| 167 | +``` |
| 168 | +
|
| 169 | +<center> |
| 170 | +[Check out request_response on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/request_response){ .md-button } |
| 171 | +</center> |
0 commit comments