5
5
This example demonstrates how to use iceoryx in a client-server architecture
6
6
using the request-response communication pattern.
7
7
8
- ## Expected output
8
+ ## Expected output basic server-client example
9
9
10
10
[ ![ asciicast] ( https://asciinema.org/a/469913.svg )] ( https://asciinema.org/a/469913 )
11
11
12
12
## Code walkthrough
13
13
14
- ### Client
14
+ In the following scenario the client (client_cxx_waitset.cpp) is using the ` Waitset ` to wait for a response from the server
15
+ (server_cxx_listener.cpp) that uses the ` Listener ` API for taking and processing the requests.
15
16
16
- At first, the includes for the client port and request-response types and runtime are needed.
17
- <!-- [geoffrey] [geoffrey][iceoryx_examples/request_response/client_cxx_basic.cpp] [iceoryx includes] -->
17
+ The client is inspired by the ` iox-cpp-waitset-basic ` example from the ` waitset ` folder and the server from the
18
+ ` iox-cpp-callbacks-subscriber ` example in the ` callbacks ` folder.
19
+
20
+ This is the most recommended way to create an efficient server-client combination with iceoryx.
21
+
22
+ ### Client using Waitset
23
+
24
+ At first, the includes for the client port, request-response types, WaitSet, and runtime are needed.
25
+ <!-- [geoffrey] [geoffrey][iceoryx_examples/request_response/client_cxx_waitset.cpp] [iceoryx includes] -->
18
26
``` cpp
19
27
#include " request_and_response_types.hpp"
20
28
21
29
#include " iceoryx_hoofs/posix_wrapper/signal_watcher.hpp"
22
30
#include " iceoryx_posh/popo/client.hpp"
31
+ #include " iceoryx_posh/popo/wait_set.hpp"
23
32
#include " iceoryx_posh/runtime/posh_runtime.hpp"
24
33
```
25
34
26
35
Next, the iceoryx runtime is initialized. With this call,
27
36
the application will be registered at ` RouDi ` , the routing and discovery daemon.
28
- <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic .cpp] [initialize runtime] -->
37
+ <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_waitset .cpp] [initialize runtime] -->
29
38
``` cpp
30
- constexpr char APP_NAME[] = " iox-cpp-request-response-client-basic" ;
31
- iox::runtime::PoshRuntime::initRuntime (APP_NAME);
39
+ iox::runtime::PoshRuntime::initRuntime ("iox-cpp-waitset-basic");
32
40
```
33
41
34
- After creating the runtime, the client port is created based on a ServiceDescription.
35
- <!--[geoffrey][iceoryx_examples/request_response/client_cxx_basic.cpp][create client]-->
42
+ After creating the runtime, the client port is created and attached to the Waitset.
43
+
44
+ <!--[geoffrey][iceoryx_examples/request_response/client_cxx_waitset.cpp][create client]-->
36
45
```cpp
37
46
iox::popo::Client<AddRequest, AddResponse> client({"Example", "Request-Response", "Add"});
38
47
```
39
48
49
+ <!-- [geoffrey][iceoryx_examples/request_response/client_cxx_waitset.cpp][create waitset]-->
50
+ ``` cpp
51
+ waitset.emplace();
52
+ waitset->attachState (client, iox::popo::ClientState::HAS_RESPONSE).or_else([ ] ( auto ) {
53
+ std::cerr << "failed to attach client" << std::endl;
54
+ std::exit(EXIT_FAILURE);
55
+ });
56
+ ```
57
+
40
58
The main goal of the client is to request from the server the sum of two numbers that the
41
59
client sends. When to sum is received from the server, the received sum is re-used to insert
42
60
it to the `addend` of the next request to send.
43
61
This calculates a Fibonacci sequence.
44
- <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic .cpp] [[send requests in a loop ]] -->
62
+ <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_waitset .cpp] [[mainloop ]] -->
45
63
```cpp
46
- uint64_t fibonacciLast = 0 ;
47
- uint64_t fibonacciCurrent = 1 ;
48
- int64_t requestSequenceId = 0 ;
49
- int64_t expectedResponseSequenceId = requestSequenceId;
50
64
while (!iox::posix::hasTerminationRequested())
51
65
{
52
66
// send request to server for sum up two numbers
53
67
54
- // the server polls with an interval of 100ms
55
- constexpr std::chrono::milliseconds DELAY_TIME{150U};
56
- std::this_thread::sleep_for (DELAY_TIME);
68
+ // We block and wait for samples to arrive, when the ime is up we send the request again
69
+ auto notificationVector = waitset.timedWait(iox::units::Duration::fromSeconds(5));
57
70
58
- // receive sum
71
+ // when response received, take the sample and process it
59
72
60
73
constexpr std::chrono::milliseconds SLEEP_TIME{950U};
61
74
std::this_thread::sleep_for(SLEEP_TIME);
@@ -68,16 +81,17 @@ Additionally, the sample is marked with a sequence id that is incremented before
68
81
every send cycle to ensure a correct ordering of the messages
69
82
(` request.getRequestHeader().setSequenceId() ` ).
70
83
The request is transmitted to the server via the ` send() ` API.
71
- <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic .cpp] [[send request]] -->
84
+ <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_waitset .cpp] [[send request]] -->
72
85
``` cpp
73
86
client.loan()
74
87
.and_then([&](auto & request) {
75
- request.getRequestHeader().setSequenceId(requestSequenceId);
76
- expectedResponseSequenceId = requestSequenceId;
77
- requestSequenceId += 1;
78
- request->augend = fibonacciLast;
79
- request->addend = fibonacciCurrent;
80
- std::cout << APP_NAME << " Send Request: " << fibonacciLast << " + " << fibonacciCurrent << std::endl;
88
+ request.getRequestHeader().setSequenceId(ctx.requestSequenceId);
89
+ ctx.expectedResponseSequenceId = ctx.requestSequenceId;
90
+ ctx.requestSequenceId += 1;
91
+ request->augend = ctx.fibonacciLast;
92
+ request->addend = ctx.fibonacciCurrent;
93
+ std::cout << APP_NAME << " Send Request: " << ctx.fibonacciLast << " + " << ctx.fibonacciCurrent
94
+ << std::endl;
81
95
request.send();
82
96
})
83
97
.or_else([](auto& error) {
@@ -90,79 +104,109 @@ and extract the sequence id with `response.getResponseHeader().getSequenceId()`.
90
104
When the server response comes in the correct order, the received sum is re-used to
91
105
insert it to the `addend` of the next request to send.
92
106
93
- <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_basic .cpp] [[take response]] -->
107
+ <!-- [geoffrey] [iceoryx_examples/request_response/client_cxx_waitset .cpp] [[take response]] -->
94
108
```cpp
95
109
while (client.take().and_then([&](const auto& response) {
96
110
auto receivedSequenceId = response.getResponseHeader().getSequenceId();
97
- if (receivedSequenceId == expectedResponseSequenceId)
111
+ if (receivedSequenceId == ctx. expectedResponseSequenceId)
98
112
{
99
- fibonacciLast = fibonacciCurrent;
100
- fibonacciCurrent = response->sum;
101
- std::cout << APP_NAME << " Got Response : " << fibonacciCurrent << std::endl;
113
+ ctx. fibonacciLast = ctx. fibonacciCurrent;
114
+ ctx. fibonacciCurrent = response->sum;
115
+ std::cout << APP_NAME << " Got Response : " << ctx. fibonacciCurrent << std::endl;
102
116
}
103
117
else
104
118
{
105
- std::cout << "Got Response with outdated sequence ID! Expected = " << expectedResponseSequenceId
106
- << "; Actual = " << receivedSequenceId << "! -> skip" << std::endl;
119
+ std::cout << "Got Response with outdated sequence ID! Expected = "
120
+ << ctx.expectedResponseSequenceId << "; Actual = " << receivedSequenceId
121
+ << "! -> skip" << std::endl;
107
122
}
108
123
}))
109
124
{
110
- };
125
+ }
111
126
```
112
127
113
- ### Server
128
+ ### Server using Listener
114
129
115
130
At first, the includes for the server port and request-response types and runtime are needed.
116
- <!-- [geoffrey] [geoffrey][iceoryx_examples/request_response/server_cxx_basic .cpp] [iceoryx includes] -->
131
+ <!-- [geoffrey] [geoffrey][iceoryx_examples/request_response/server_cxx_listener .cpp] [iceoryx includes] -->
117
132
``` cpp
118
133
#include " request_and_response_types.hpp"
119
134
120
135
#include " iceoryx_hoofs/posix_wrapper/signal_watcher.hpp"
136
+ #include " iceoryx_posh/popo/listener.hpp"
137
+ #include " iceoryx_posh/popo/notification_callback.hpp"
121
138
#include " iceoryx_posh/popo/server.hpp"
122
139
#include " iceoryx_posh/runtime/posh_runtime.hpp"
123
140
```
124
141
125
- Next, the iceoryx runtime is initialized. With this call,
126
- the application will be registered at ` RouDi ` , the routing and discovery daemon.
127
- <!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_basic.cpp] [initialize runtime] -->
142
+ First, a callback is created that shall be called when the server receives a response.
143
+ In this case the calculation and the sending of the response is done in the listener callback.
144
+ If there are more resource-consuming tasks,
145
+ this could also be outsourced with a thread pool to handle the requests
146
+
147
+ <!-- [geoffrey][iceoryx_examples/request_response/server_cxx_listener.cpp][request callback]-->
148
+ ``` cpp
149
+ void onRequestReceived (iox::popo::Server<AddRequest, AddResponse>* server)
150
+ {
151
+ while (server->take().and_then([ &] (const auto& request) {
152
+ std::cout << APP_NAME << " Got Request: " << request->augend << " + " << request->addend << std::endl;
153
+
154
+ server->loan(request)
155
+ .and_then([&](auto& response) {
156
+ response->sum = request->augend + request->addend;
157
+ std::cout << APP_NAME << " Send Response: " << response->sum << std::endl;
158
+ response.send();
159
+ })
160
+ .or_else([](auto& error) {
161
+ std::cout << "Could not allocate Response! Return value = " << static_cast<uint64_t>(error)
162
+ << std::endl;
163
+ });
164
+ }))
165
+ {
166
+ }
167
+ }
168
+ ```
169
+
170
+ Maybe: The server provides the `take()` method for receiving requests and the `loan()` and `send()` methods
171
+ for sending the responses with the sum of the two numbers.
172
+
173
+ Next, the iceoryx runtime is initialized.
174
+ <!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_listener.cpp] [initialize runtime] -->
128
175
```cpp
129
176
constexpr char APP_NAME[] = "iox-cpp-request-response-server-basic;
130
177
iox::runtime::PoshRuntime::initRuntime(APP_NAME);
131
178
```
132
179
133
180
After creating the runtime, the server port is created based on a ServiceDescription.
134
- <!--[geoffrey][iceoryx_examples/request_response/server_cxx_basic .cpp][create server]-->
181
+ <!-- [geoffrey][iceoryx_examples/request_response/server_cxx_listener .cpp][create server]-->
135
182
``` cpp
136
183
iox::popo::Server<AddRequest, AddResponse> server ({"Example", "Request-Response", "Add"});
137
184
```
138
185
139
- In the main loop, the server receives the requests from the client with
140
- `take()` and prepares with `loan()` a sample for the response.
141
- The received two numbers `augend` and `addend` are added and transmitted
142
- with the `send()` API.
143
- <!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_basic.cpp] [[process requests in a loop] [take request] [send response]] -->
186
+ Now we want to listen to an incoming server event and want to fire the previously created callback.
187
+ This is done with the following call:
188
+ <!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_listener.cpp][attach listener] -->
144
189
```cpp
145
- while (!iox::posix::hasTerminationRequested())
146
- {
147
- server.take().and_then([&](const auto& request) {
148
- std::cout << APP_NAME << " Got Request: " << request->augend << " + " << request->addend << std::endl;
149
-
150
- server.loan(request)
151
- .and_then([&](auto& response) {
152
- response->sum = request->augend + request->addend;
153
- std::cout << APP_NAME << " Send Response: " << response->sum << std::endl;
154
- response.send();
155
- })
156
- .or_else([&](auto& error) {
157
- std::cout << APP_NAME
158
- << " Could not allocate Response! Return value = " << static_cast<uint64_t>(error)
159
- << std::endl;
160
- });
161
- });
190
+ listener
191
+ .attachEvent(
192
+ server, iox::popo::ServerEvent::REQUEST_RECEIVED, iox::popo::createNotificationCallback(onRequestReceived))
193
+ .or_else([](auto) {
194
+ std::cerr << "unable to attach server" << std::endl;
195
+ std::exit(EXIT_FAILURE);
196
+ });
197
+ ```
162
198
163
- constexpr std::chrono::milliseconds SLEEP_TIME{100U};
164
- std::this_thread::sleep_for(SLEEP_TIME);
165
- }
199
+ With that the preparation is done and the main thread can just sleep or do other things:
200
+
201
+ <!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_listener.cpp][wait for termination] -->
202
+ ``` cpp
203
+ iox::posix::waitForTerminationRequest ();
204
+ ```
205
+
206
+ Once the user wants to shutdown the server, the server event is detached from the listener:
207
+ <!-- [geoffrey] [iceoryx_examples/request_response/server_cxx_listener.cpp][wait for termination] -->
208
+ ``` cpp
209
+ listener.detachEvent(server, iox::popo::ServerEvent::REQUEST_RECEIVED);
166
210
```
167
211
168
212
<center >
0 commit comments