@@ -32,7 +32,7 @@ auto async_main() -> net::awaitable<void>
32
32
req.push("QUIT");
33
33
34
34
// Responses as tuple elements.
35
- std::tuple<aedis:: ignore, std::map<std::string, std::string>, aedis:: ignore> resp;
35
+ std::tuple<ignore, std::map<std::string, std::string>, ignore> resp;
36
36
37
37
// Executes the request and reads the response.
38
38
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
@@ -41,129 +41,135 @@ auto async_main() -> net::awaitable<void>
41
41
}
42
42
```
43
43
44
- For different versions of this example using different styles see
44
+ For other versions of this example that use different styles see
45
45
46
- * cpp20_intro.cpp: Does not use awaitable operators
47
- * cpp20_intro_awaitable_ops.cpp: The version above.
48
- * cpp17_intro.cpp: Requires C++17 only .
46
+ * cpp20_intro.cpp: Does not use awaitable operators.
47
+ * cpp20_intro_awaitable_ops.cpp: The version from above.
48
+ * cpp17_intro.cpp: Uses callbacks and requires C++17.
49
49
* cpp20_intro_tls.cpp: Communicates over TLS.
50
50
51
51
The execution of ` connection::async_exec ` above is composed with
52
52
` connection::async_run ` with the aid of the Asio awaitable ` operator || `
53
53
that ensures that one operation is cancelled as soon as the other
54
- completes, these functions play the following roles
54
+ completes. These functions play the following roles
55
55
56
56
* ` connection::async_exec ` : Execute commands by queuing the request
57
57
for writing. It will wait for the response sent back by Redis and
58
58
can be called from multiple places in your code concurrently.
59
59
* ` connection::async_run ` : Coordinate low-level read and write
60
60
operations. More specifically, it will hand IO control to
61
- ` async_exec ` when a response arrives, to
62
- ` async_receive ` when a server-push is received
63
- and will trigger writes of pending requests when a reconnection
64
- occurs.
61
+ ` async_exec ` when a response arrives and to ` async_receive ` when a
62
+ server-push is received. It will also trigger writes of pending
63
+ requests when a reconnection occurs.
65
64
66
65
The role played by ` async_run ` can be better understood in the context
67
66
of long-lived connections, which we will cover in the next section.
68
- Before that however, the reader might want to skim over some further examples
69
-
70
- * cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions.
71
- * cpp20_serialization.cpp: Shows how to serialize types using Boost.Json.
72
- * cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.
73
- * cpp20_subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription.
74
- * cpp20_echo_server.cpp: A simple TCP echo server.
75
- * cpp20_chat_room.cpp: A command line chat built on Redis pubsub.
76
- * cpp20_low_level_async.cpp: Sends a ping asynchronously using the low-level API.
77
- * cpp17_low_level_sync.cpp: Sends a ping synchronously using the low-level API.
78
-
79
- To avoid repetition code that is common to some examples has been
80
- grouped in common.hpp. The main function used in some async examples
81
- has been factored out in the main.cpp file.
82
67
83
68
<a name =" connection " ></a >
84
69
## Connection
85
70
86
71
For performance reasons we will usually want to perform multiple
87
- requests on the same connection. We can do this with the example above
88
- by decoupling the ` HELLO ` command and the call to ` async_run ` in a
89
- separate coroutine
72
+ requests in the same connection. We can do this in the example above
73
+ by letting ` async_run ` run detached in a separate coroutine, for
74
+ example (see cpp20_intro.cpp)
90
75
91
76
``` cpp
92
77
auto run (std::shared_ptr<connection > conn) -> net::awaitable<void >
93
78
{
94
79
co_await connect(conn, "127.0.0.1", "6379");
80
+ co_await conn->async_run();
81
+ }
95
82
83
+ auto hello(std::shared_ptr<connection > conn) -> net::awaitable<void >
84
+ {
96
85
resp3::request req;
97
- req.push("HELLO", 3); // Upgrade to RESP3
86
+ req.push("HELLO", 3);
98
87
99
- // Notice we use && instead of || so async_run is not cancelled
100
- // when the HELLO response arrives. We are also ignoring the
101
- // response for simplicity.
102
- co_await (conn->async_run() && conn->async_exec(req));
88
+ co_await conn->async_exec(req);
103
89
}
104
- ```
105
- We can now let `run` run detached in the background while other
106
- coroutines perform requests on the connection, for example
107
90
108
- ```cpp
109
- auto async_main() -> net::awaitable<void>
91
+ auto ping(std::shared_ptr<connection > conn) -> net::awaitable<void >
110
92
{
111
- auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
112
-
113
- // Run detached.
114
- net::co_spawn(ex, run(conn), net::detached);
115
-
116
- // Here we can use the connection to perform requests and pass it
117
- // around to other coroutines so they can make requests.
118
-
119
93
resp3::request req;
120
94
req.push("PING", "Hello world");
121
- co_await conn->async_exec( req);
95
+ req.push("QUIT" );
122
96
123
- ...
97
+ std::tuple<std::string, aedis::ignore> resp;
98
+ co_await conn->async_exec(req, adapt(resp));
99
+ // Use the response ...
100
+ }
124
101
125
- // Cancels the run operation so we can exit.
126
- conn->cancel(operation::run);
102
+ auto async_main() -> net::awaitable<void >
103
+ {
104
+ auto ex = co_await net::this_coro::executor;
105
+ auto conn = std::make_shared<connection >(ex);
106
+ net::co_spawn(ex, run(conn), net::detached);
107
+ co_await hello(conn);
108
+ co_await ping(conn);
109
+
110
+ // Here we can pass conn to other coroutines that need to
111
+ // communicate with Redis.
127
112
}
128
113
```
129
114
130
- With this separation, it is now easy to incorporate other operations
131
- in our application, for example, to cancel the connection on ` SIGINT `
132
- and ` SIGTERM ` we can extend ` run ` as follows
115
+ With this separation, it is now easy to incorporate other long-running
116
+ operations in our application, for example, the run coroutine below
117
+ adds signal handling and a healthy checker
133
118
134
119
```cpp
135
120
auto run(std::shared_ptr<connection> conn) -> net::awaitable<void>
136
121
{
137
- co_await connect(conn, "127.0.0.1", "6379");
138
122
signal_set sig{ex, SIGINT, SIGTERM};
123
+ co_await connect(conn, "127.0.0.1", "6379");
124
+ co_await (conn->async_run() || sig.async_wait() || healthy_checker(conn));
125
+ }
126
+ ```
139
127
140
- resp3::request req;
141
- req.push("HELLO", 3);
128
+ Here we use Asio awaitable operator for simplicity, the same
129
+ functionality can be achieved by means of the
130
+ ` aedis::connection::cancel ` function. The definition of the
131
+ ` healthy_checker ` used above can be found in common.cpp.
132
+
133
+ ### Server pushes
142
134
143
- co_await ((conn->async_run() || sig.async_wait()) && conn->async_exec(req));
135
+ Redis servers can also send a variety of pushes to the client, some of
136
+ them are
137
+
138
+ * [ Pubsub] ( https://redis.io/docs/manual/pubsub/ )
139
+ * [ Keyspace notification] ( https://redis.io/docs/manual/keyspace-notifications/ )
140
+ * [ Client-side caching] ( https://redis.io/docs/manual/client-side-caching/ )
141
+
142
+ The connection class supports that by means of the
143
+ ` aedis::connection::async_receive ` function
144
+
145
+ ``` cpp
146
+ auto receiver (std::shared_ptr<connection > conn) -> net::awaitable<void >
147
+ {
148
+ using resp_type = std::vector< resp3::node<std::string > >;
149
+ for (resp_type resp;;) {
150
+ co_await conn->async_receive(adapt(resp));
151
+ // Use resp and clear the response for a new push.
152
+ resp.clear();
153
+ }
144
154
}
145
155
```
146
156
147
- Likewise we can incorporate support for server pushes, healthy checks and pubsub
157
+ This function can be also easily incorporated in the run function from
158
+ above, for example
148
159
149
160
```cpp
150
161
auto run(std::shared_ptr<connection> conn) -> net::awaitable<void>
151
162
{
152
- co_await connect(conn, "127.0.0.1", "6379");
153
163
signal_set sig{ex, SIGINT, SIGTERM};
154
-
155
- resp3::request req;
156
- req.push("HELLO", 3);
157
- req.push("SUBSCRIBE", "channel1", "channel2");
158
-
159
- co_await ((conn->async_run() || sig.async_wait() || receiver(conn) || healthy_checker(conn))
160
- && conn->async_exec(req));
164
+ co_await connect(conn, "127.0.0.1", "6379");
165
+ co_await (conn->async_run() || sig.async_wait() || healthy_checker(conn) || receiver(conn));
161
166
}
162
167
```
163
168
164
- The definition of ` receiver ` and ` healthy_checker ` above can be found
165
- in cpp20_subscriber.cpp. Adding a loop around ` async_run ` produces a simple
166
- way to support reconnection _ while there are pending operations on the connection_ ,
169
+ ### Reconnecting
170
+
171
+ Adding a loop around ` async_run ` produces a simple way to support
172
+ reconnection _ while there are pending operations on the connection_ ,
167
173
for example, to reconnect to the same address
168
174
169
175
``` cpp
@@ -172,15 +178,11 @@ auto run(std::shared_ptr<connection> conn) -> net::awaitable<void>
172
178
auto ex = co_await net::this_coro::executor;
173
179
steady_timer timer{ex};
174
180
175
- resp3::request req;
176
- req.push("HELLO", 3);
177
- req.push("SUBSCRIBE", "channel1", "channel2");
178
-
179
181
for (;;) {
180
182
co_await connect(conn, "127.0.0.1", "6379");
181
- co_await (( conn->async_run() || healthy_checker(conn) || receiver(conn)) && conn->async_exec(req) );
183
+ co_await (conn->async_run() || healthy_checker(conn) || receiver(conn);
182
184
183
- // Prepare the stream to a new connection.
185
+ // Prepare the stream for a new connection.
184
186
conn->reset_stream();
185
187
186
188
// Waits one second before trying to reconnect.
@@ -225,8 +227,6 @@ co_await (conn.async_exec(...) || time.async_wait(...))
225
227
226
228
* Provides a way to limit how long the execution of a single request
227
229
should last.
228
- * The cancellation will be ignored if the request has already
229
- been written to the socket.
230
230
* NOTE: It is usually a better idea to have a healthy checker than adding
231
231
per request timeout, see cpp20_subscriber.cpp for an example.
232
232
@@ -242,8 +242,8 @@ co_await (conn.async_exec(...) || conn.async_exec(...) || ... || conn.async_exec
242
242
243
243
* This works but is unnecessary. Unless the user has set
244
244
` aedis::resp3::request::config::coalesce ` to ` false ` , and he
245
- shouldn't, the connection will automatically merge the individual
246
- requests into a single payload anyway.
245
+ usually shouldn't, the connection will automatically merge the
246
+ individual requests into a single payload anyway.
247
247
248
248
<a name =" requests " ></a >
249
249
## Requests
@@ -583,6 +583,23 @@ In addition to the above users can also use unordered versions of the
583
583
containers. The same reasoning also applies to sets e.g. ` SMEMBERS `
584
584
and other data structures in general.
585
585
586
+ ## Examples
587
+
588
+ The examples below show how to use the features discussed so far
589
+
590
+ * cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions.
591
+ * cpp20_serialization.cpp: Shows how to serialize types using Boost.Json.
592
+ * cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.
593
+ * cpp20_subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription.
594
+ * cpp20_echo_server.cpp: A simple TCP echo server.
595
+ * cpp20_chat_room.cpp: A command line chat built on Redis pubsub.
596
+ * cpp20_low_level_async.cpp: Sends a ping asynchronously using the low-level API.
597
+ * cpp17_low_level_sync.cpp: Sends a ping synchronously using the low-level API.
598
+
599
+ To avoid repetition code that is common to some examples has been
600
+ grouped in common.hpp. The main function used in some async examples
601
+ has been factored out in the main.cpp file.
602
+
586
603
## Echo server benchmark
587
604
588
605
This document benchmarks the performance of TCP echo servers I
0 commit comments