diff --git a/content/develop/clients/hiredis/_index.md b/content/develop/clients/hiredis/_index.md index 77c5ee3be1..614ca06d21 100644 --- a/content/develop/clients/hiredis/_index.md +++ b/content/develop/clients/hiredis/_index.md @@ -30,8 +30,6 @@ Then, in a terminal, go into the `hiredis` folder and run the `make` command to the dynamically-loaded library for `hiredis` (this has the name `libhiredis.dylib` on MacOS and `libhiredis.so` on Linux). You can copy this library to your project folder or run `sudo make install` to install it to `/usr/local/lib`. -You should also copy the header files `hiredis.h`, `alloc.h`, `read.h`, and -`sds.h` to your project. ## Connect and test @@ -43,7 +41,7 @@ connection. An explanation of the code follows the example. ```c #include -#include "hiredis.h" +#include int main() { // The `redisContext` type represents the connection @@ -81,8 +79,9 @@ int main() { For a real project, you would build your code with a makefile, but for this simple test, you can just place it in a file called `main.c` and -build it with the following command (assuming you used `make install` to -install the `libhiredis` library): +build it with the following command. (If you didn't install `hiredis` +using `make install`, then you should also use the `-I` option to +specify the folder that contains the `hiredis` headers.) ```bash cc main.c -L/usr/local/lib -lhiredis @@ -123,11 +122,7 @@ to prevent errors. The [`hiredis`](https://github.com/redis/hiredis) Github repository contains examples and details that may be useful if you are using `hiredis` to implement a higher-level client for another programming language. There are -also examples showing how to use `hiredis` from a -[C++ application](https://github.com/redis/hiredis/blob/master/examples/example-qt.cpp) -created with [Qt](https://www.qt.io/) and how to use the -[asynchronous API](https://github.com/redis/hiredis?tab=readme-ov-file#asynchronous-api) -with the [libev](https://software.schmorp.de/pkg/libev.html) and -[libevent](https://libevent.org/) libraries. +also examples showing how to use `hiredis` adapter headers to integrate with +various event handling frameworks. See the other pages in this section for more information and examples. diff --git a/content/develop/clients/hiredis/connect.md b/content/develop/clients/hiredis/connect.md index 6eb1c25c39..d9d98dd79b 100644 --- a/content/develop/clients/hiredis/connect.md +++ b/content/develop/clients/hiredis/connect.md @@ -25,7 +25,7 @@ and port as its arguments, and returns a context object. ```c #include -#include "hiredis.h" +#include . . . @@ -62,4 +62,84 @@ freeReplyObject(reply); redisFree(c); ``` +## Asynchronous connection +You can also connect to Redis using an asynchronous API. +The `redisAsyncConnect()` call that creates the context is +similar to the synchronous function `redisConnect()`, but it returns the +context object immediately before the connection is complete. +It lets you supply callbacks to respond when a connection is successful +or to handle any errors that may occur. + +The following code creates an asynchronous connection and +sets the context callbacks. Note that you must also include the +`async.h` header to access the asynchronous API. + +```c +#include + +#include +#include + . + . + . + +redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + +if (c->err) { + printf("Error: %s\n", c->errstr); + return 1; +} + +// Set callbacks to respond to successful or unsuccessful +// connection and disconnection. +redisAsyncSetConnectCallback(c, connectCallback); +redisAsyncSetDisconnectCallback(c, disconnectCallback); + +char *key = "testkey"; +char *value = "testvalue"; + +// Status reply is ignored. +redisAsyncCommand(c, NULL, NULL, "SET %s %s", key, value); + +// Reply handled by `getCallback()` function. +redisAsyncCommand(c, getCallback, key, "GET %s", key); +``` + +The callback functions have a simple signature that receives +the context object and a status code. See +[Handling errors]({{< relref "/develop/clients/hiredis/handle-replies#handling-errors" >}}) +for a list of the possible status codes. + +```c +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} +``` + +Use the `redisAsyncCommand()` function to issue Redis commands +with an asynchronous connection. This is similar to the equivalent +synchronous function `redisCommand()` but also lets you supply a callback +and a custom data pointer to process the response to the command. See +[Construct asynchronous commands]({{< relref "/develop/clients/hiredis/issue-commands#construct-asynchronous-commands" >}}) for more +information. + +Note that you should normally disconnect asynchronously from a +callback when you have finished using the connection. +Use `redisAsyncDisconnect()` to disconnect gracefully, letting +pending commands execute and activate their callbacks. +Use `redisAsyncFree()` to disconnect immediately. If you do this then +any pending callbacks from commands that have already executed will be +called with a `NULL` reply pointer. diff --git a/content/develop/clients/hiredis/int-examples/_index.md b/content/develop/clients/hiredis/int-examples/_index.md new file mode 100644 index 0000000000..bb4abd0535 --- /dev/null +++ b/content/develop/clients/hiredis/int-examples/_index.md @@ -0,0 +1,22 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Integrate hiredis with C++ and external frameworks. +linkTitle: Integration guides +title: Integration guides +weight: 50 +--- + +`hiredis` is compatible with C++ and the library source includes a set of +[adapters](https://github.com/redis/hiredis/tree/master/adapters) +to help you use it in conjunction with C and C++ libraries and frameworks. +The pages in this section explain how to integrate `hiredis` into +your app. diff --git a/content/develop/clients/hiredis/int-examples/libevent-integration.md b/content/develop/clients/hiredis/int-examples/libevent-integration.md new file mode 100644 index 0000000000..0d8633ed19 --- /dev/null +++ b/content/develop/clients/hiredis/int-examples/libevent-integration.md @@ -0,0 +1,166 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Use `hiredis` in conjunction with the `libevent` framework. +linkTitle: libevent integration +title: Integrate hiredis with a libevent app +weight: 60 +--- + +The [`libevent`](https://libevent.org/) library provides an +implementation of an event loop that lets you call functions +asynchronously in response to events. This guide explains +how to use `hiredis` to connect to a Redis server from a +`libevent` app. + +## Install `libevent` + +The [`libevent` home page](https://libevent.org/) has links to download +all versions of the library, but you should use the latest version +unless there is a specific version you need to target. + +When you have downloaded `libevent`, follow the instructions in the +`README` file to compile and install the library. + +## Create a simple app + +For a real project, you would build your code with a makefile, but for +this simple test, you can just place it in a file called `main.c` and +build it with the following command (assuming you used `make install` to +install the `libhiredis` and `libevent` libraries): + +```bash +cc main.c -L/usr/local/lib -lhiredis -levent +``` + +See [Build and install]({{< relref "/develop/clients/hiredis#build-and-install" >}}) +to learn how to build `hiredis`, if you have not already done so. + +Now, add the following code in `main.c`. An explanation follows the +code example: + +```c +#include +#include +#include +#include + +#include +#include +#include + +// Callback for the `GET` command. +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + char *key = privdata; + + if (reply == NULL) { + if (c->errstr) { + printf("errstr: %s\n", c->errstr); + } + return; + } + + printf("Key: %s, value: %s\n", key, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +// Callback to respond to successful or unsuccessful connection. +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +// Callback to respond to intentional or unexpected disconnection. +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + + +int main (int argc, char **argv) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + // Create the libevent `event_base` object to track all + // events. + struct event_base *base = event_base_new(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + + if (c->err) { + printf("Error: %s\n", c->errstr); + return 1; + } + + // Use the Redis libevent adapter to attach the Redis connection + // to the libevent main loop. + redisLibeventAttach(c,base); + + redisAsyncSetConnectCallback(c, connectCallback); + redisAsyncSetDisconnectCallback(c, disconnectCallback); + + char *key = "testkey"; + char *value = "testvalue"; + + redisAsyncCommand(c, NULL, NULL, "SET %s %s", key, value); + redisAsyncCommand(c, getCallback, key, "GET %s", key); + + // Run the event loop. + event_base_dispatch(base); + + return 0; +} +``` + +The code calls +[`event_base_new()`](https://libevent.org/doc/event_8h.html#af34c025430d445427a2a5661082405c3) +to initialize the core +[`event_base`](https://libevent.org/doc/structevent__base.html) +object that manages the event loop. It then creates a standard +[asynchronous connection]({{< relref "/develop/clients/hiredis/connect#asynchronous-connection" >}}) +to Redis and uses the `libevent` adapter function `redisLibeventAttach()` to +attach the connection to the event loop. + +After setting the [connection callbacks]({{< relref "/develop/clients/hiredis/connect#asynchronous-connection" >}}), the code issues two asynchronous +Redis commands (see +[Construct asynchronous commands]({{< relref "/develop/clients/hiredis/issue-commands#construct-asynchronous-commands" >}}) +for more information). +The final step is to call +[`event_base_dispatch()`](https://libevent.org/doc/event_8h.html#a19d60cb72a1af398247f40e92cf07056) +to start the event loop. This will wait for the commands to be processed and +then exit when the Redis connection is closed in the `getCallback()` function. + +## Run the code + +If you compile and run the code, you will see the following output, +showing that the callbacks executed correctly: + +``` +Connected... +Key: testkey, value: testvalue +Disconnected... +``` + +You can use the +[`KEYS`]({{< relref "/commands/keys" >}}) command from +[`redis-cli`]({{< relref "/develop/tools/cli" >}}) or +[Redis Insight]({{< relref "/develop/tools/insight" >}}) to check +that the "testkey" string key was added to the Redis database. diff --git a/content/develop/clients/hiredis/int-examples/qt-integration.md b/content/develop/clients/hiredis/int-examples/qt-integration.md new file mode 100644 index 0000000000..96dd248f2a --- /dev/null +++ b/content/develop/clients/hiredis/int-examples/qt-integration.md @@ -0,0 +1,324 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Use `hiredis` in conjunction with the Qt app framework. +linkTitle: Qt integration +title: Integrate hiredis with a Qt app +weight: 50 +--- + +[Qt](https://www.qt.io/) is a popular cross-platform C++ framework that +you can use to build command line and GUI apps. This guide explains how +to use `hiredis` to connect to a Redis server from a Qt app. + +## Install Qt + +You should first download and install the +[Qt development environment](https://www.qt.io/download-dev) for your +development platform, if you have not already done so. The example +below briefly explains how to use Qt Creator +to manage your project, but see the [Qt Creator](https://doc.qt.io/qtcreator/) +docs for an extensive set of examples and tutorials. + +## Create a simple app + +We will use a simple console app to demonstrate how to connect +to Redis from Qt. Create the app project in Qt Creator using the +**File > New Project** command. The generated source code is a +single C++ file, called `main.cpp`, that uses a +[`QCoreApplication`](https://doc.qt.io/qt-6/qcoreapplication.html) +object to handle the main event loop. Although it will compile and run, +it doesn't do anything useful at this stage. + +## Add `hiredis` files + +Build `hiredis` if you have not already done so (see +[Build and install]({{< relref "/develop/clients/hiredis#build-and-install" >}}) +for more information). + +You should also make the `libhiredis` library available to the project. For example, +if you have used the default option of [`cmake`](https://cmake.org/) as the project +build tool and you have installed the `.dylib` or `.so` file for `hiredis` in `/usr/local/lib`, +you should add the following lines to the `CMakeLists.txt` file: + +``` +add_library(hiredis SHARED IMPORTED) +set_property(TARGET hiredis PROPERTY + IMPORTED_LOCATION "/usr/local/lib/libhiredis.dylib") +``` + +You should also modify the `target_link_libraries` directive to include +`hiredis`: + +``` +target_link_libraries(ConsoleTest Qt${QT_VERSION_MAJOR}::Core hiredis) +``` + +## Add code to access Redis + +You can add a class using the **Add new** context menu +on the project folder in Qt Creator. The sections below give +examples of the code you should add to this class to +connect to Redis. The code is separated into header and +implementation files. + +### Header file + +The header file for a class called `RedisExample` is shown below. +An explanation follows the code. + +```c++ +// redisexample.h + +#ifndef REDISEXAMPLE_H +#define REDISEXAMPLE_H + +#include + +#include +#include +#include + + +class RedisExample : public QObject +{ + Q_OBJECT + +public: + // Constructor + RedisExample(const char *keyForRedis, const char *valueForRedis, QObject *parent = 0) + :QObject(parent), m_key(keyForRedis), m_value(valueForRedis) {} + +public slots: + // Slot method to hold the code that connects to Redis and issues + // commands. + void run(); + +signals: + // Signal to indicate that our code has finished executing. + void finished(); + +public: + // Method to close the Redis connection and signal that we've + // finished. + void finish(); + +private: + const char *m_key; // Key for Redis string. + const char *m_value; // Value for Redis string. + redisAsyncContext *m_ctx; // Redis connection context. + RedisQtAdapter m_adapter; // Adapter to let `hiredis` work with Qt. +}; + +#endif // REDISEXAMPLE_H +``` + +[`QObject`](https://doc.qt.io/qt-6/qobject.html) is a key Qt class that +implements the [Object model](https://doc.qt.io/qt-6/object.html) for +communication between objects. When you create your class in Qt Creator, +you can specify that you want it to be a subclass of `QObject` (this will +add the appropriate header files and include the `Q_OBJECT` macro in the +class declaration). + +The `QObject` communication model uses some instance methods as *signals* +to report events and others as *slots* to act as callbacks that process the +events (see [Signals and slots](https://doc.qt.io/qt-6/signalsandslots.html) +for an introduction). The Qt [meta-object compiler](https://doc.qt.io/qt-6/moc.html) +recognizes the non-standard C++ access specifiers `signals:` and `slots:` in the +class declaration and adds extra code for them during compilation to enable +the communication mechanism. + +In our class, there is a `run()` slot that will implement the code to access Redis. +The code eventually emits a `finished()` signal when it is complete to indicate that +the app should exit. + +Our simple example code just sets and gets a Redis +[string]({{< relref "/develop/data-types/strings" >}}) key. The class contains +private attributes for the key and value (following the Qt `m_xxx` naming convention +for class members). These are set by the constructor along with a call to the +`QObject` constructor. The other attributes represent the connection context for +Redis (which should generally be +[asynchronous]({{< relref "/develop/clients/hiredis/connect#asynchronous-connection" >}}) +for a Qt app) and an adapter object that `hiredis` uses to integrate with Qt. + +### Implementation file + +The file that implements the methods declared in the header is shown +below. A full explanation follows the code. + +```c++ +// redisexample.cpp + +#include + +#include "redisexample.h" + + +void RedisExample::finish() { + // Disconnect gracefully. + redisAsyncDisconnect(m_ctx); + + // Emit the `finished()` signal to indicate that the + // execution is complete. + emit finished(); +} + + +// Callback used by our `GET` command in the `run()` method. +void getCallback(redisAsyncContext *, void * r, void * privdata) { + + // Cast data pointers to their appropriate types. + redisReply *reply = static_cast(r); + RedisExample *ex = static_cast(privdata); + + if (reply == nullptr || ex == nullptr) { + return; + } + + std::cout << "Value: " << reply->str << std::endl; + + // Close the Redis connection and quit the app. + ex->finish(); +} + + +void RedisExample::run() { + // Open the connection to Redis. + m_ctx = redisAsyncConnect("localhost", 6379); + + if (m_ctx->err) { + std::cout << "Error: " << m_ctx->errstr << std::endl; + finish(); + } + + // Configure the connection to work with Qt. + m_adapter.setContext(m_ctx); + + // Issue some simple commands. For the `GET` command, pass a + // callback function and a pointer to this object instance + // so that we can access the object's members from the callback. + redisAsyncCommand(m_ctx, NULL, NULL, "SET %s %s", m_key, m_value); + redisAsyncCommand(m_ctx, getCallback, this, "GET %s", m_key); +} +``` + +The code that accesses Redis is in the `run()` method (recall that this +implements a Qt slot that will be called in response to a signal). The +code connects to Redis and stores the connection context pointer in the +`m_ctx` attribute of the class instance. The call to `m_adapter.setContext()` +initializes the Qt support for the context. Note that we need an +asynchronous connection for Qt. See +[Asynchronous connection]({{< relref "/develop/clients/hiredis/connect#asynchronous-connection" >}}) +for more information. + +The code then issues two Redis commands to [`SET`]({{< relref "/commands/set" >}}) +the string key and value that were supplied using the class's constructor. We are +not interested in the response returned by this command, but we are interested in the +response from the [`GET`]({{< relref "/commands/get" >}}) command that follows it. +Because the commands are asynchronous, we need to set a callback to handle +the `GET` response when it arrives. In the `redisAsyncCommand()` call, we pass +a pointer to our `getCallback()` function and also pass a pointer to the +`RedisExample` instance. This is a custom data field that will simply +be passed on to the callback when it executes (see +[Construct asynchronous commands]({{< relref "/develop/clients/hiredis/issue-commands#construct-asynchronous-commands" >}}) +for more information). + +The code in the `getCallback()` function starts by casting the reply pointer +parameter to [`redisReply`]({{< relref "/develop/clients/hiredis/handle-replies" >}}) +and the custom data pointer to `RedisExample`. Here, the example just prints +the reply string to the console, but you can process it in any way you like. +You can add methods to your class and call them within the callback using the +custom data pointer passed during the `redisAsyncCommand()` call. Here, we +simply use the pointer to call the `finish()` method. + +The `finish()` method calls +`redisAsyncDisconnect()` to close the connection and then uses the +Qt signalling mechanism to emit the `finished()` signal. You may need to +process several commands with a particular connection context, but you should +close it from a callback when you have finished using it. + +### Main program + +To access the `RedisExample` class, you should use code like the +following in the `main()` function defined in `main.cpp`: + +```c++ +#include +#include + +#include "redisexample.h" + + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + // Instance of our object. + RedisExample r("url", "https://redis.io/"); + + // Call the `run()` slot on our `RedisExample` instance to + // run our Redis commands. + QTimer::singleShot(0, &r, SLOT(run())); + + // Set up a communication connection between our `finished()` + // signal and the application's `quit()` slot. + QObject::connect(&r, SIGNAL(finished()), &app, SLOT(quit())); + + // Start the app's main event loop. + return app.exec(); +} +``` + +This creates the [`QCoreApplication`](https://doc.qt.io/qt-6/qcoreapplication.html) +instance that manages the main event loop for a console app. It +then creates the instance of `RedisExample` with the key ("url") and +value ("https://redis.io/") for our Redis string. + +The two lines below set up the `QObject` communication mechanism +for the app. The call to +[`QTimer::singleShot()`](https://doc.qt.io/qt-6/qtimer.html#singleShot-2) +activates the `run()` +slot method on our `RedisExample` instance. The +[`QObject::connect()`](https://doc.qt.io/qt-6/qobject.html#connect-5) +call creates a communication link between the `finished()` signal of +out `RedisExample` instance and the `quit()` slot of our +`QCoreApplication` instance. This quits the application event loop and +exits the app when the `finished()` signal is emitted by the +`RedisExample` object. This happens when the `finish()` method is called +at the end of the `GET` command callback. + +## Run the code + +When you have added the code, you can run it from the **Build** menu of +Qt Creator or from the toolbar at the left hand side of the window. +Assuming the connection to Redis succeeds, it will print the message +`Value: https://redis.io/` and quit. You can use the +[`KEYS`]({{< relref "/commands/keys" >}}) command from +[`redis-cli`]({{< relref "/develop/tools/cli" >}}) or +[Redis Insight]({{< relref "/develop/tools/insight" >}}) to check +that the "url" string key was added to the Redis database. + +## Key information + +There are many ways you could use Redis with a Qt app, but our example +demonstrates some techniques that are broadly useful: + +- Use the `QObject` communication mechanism to simplify your code. +- Use the `hiredis` asynchronous API. Add a `RedisQtAdapter` instance + to your code and ensure you call its `setContext()` method to + initialize it before issuing Redis commands. +- Place all code and data you need to interact with Redis + (including the connection context) in a single + class or ensure it is available from a class via pointers and + Qt signals. Pass a pointer to an instance of your class in the + custom data parameter when you issue a Redis command with + `redisAsyncCommand()` and use this to process the reply or + issue more commands from the callback. diff --git a/content/develop/clients/hiredis/issue-commands.md b/content/develop/clients/hiredis/issue-commands.md index aa963c23f6..036ebd6951 100644 --- a/content/develop/clients/hiredis/issue-commands.md +++ b/content/develop/clients/hiredis/issue-commands.md @@ -116,6 +116,96 @@ const size_t argvlen[] = {3, 8, 5}; redisReply *reply = redisCommandArgv(c, argc, argv, argvlen); ``` +## Construct asynchronous commands + +Use the `redisAsyncCommand()` and `redisAsyncCommandArgv()` +functions to send commands to the server asynchronously: + +```c +#include + . + . + . +int redisAsyncCommand( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + const char *format, ...); +int redisAsyncCommandArgv( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + int argc, const char **argv, const size_t *argvlen); +``` + +These work the same way as `redisCommand()` and `redisCommandArgv()` +(see [Construct synchronous commands](#construct-synchronous-commands) +above) but they have two extra parameters. The first is a pointer to +a optional callback function and the second is a pointer to your own +custom data, which will be passed to the callback when it +executes. Pass `NULL` for both of these pointers if you don't need +to use them. + +The callback has the following signature: + +```c +void(redisAsyncContext *c, void *reply, void *privdata); +``` + +The first parameter is the asynchronous connection context and +the second is a pointer to the reply object. Use a cast to +`(redisReply *)` to access the reply in the usual way (see +[Handle command replies]({{< relref "/develop/clients/hiredis/handle-replies" >}}) +for a full description of `redisReply`). The last parameter +is the custom data pointer that you supplied during the +`redisAsyncCommand()` call. This is passed to your function +without any modification. + +The example below shows how you can use `redisAsyncCommand()` with +or without a reply callback: + +```c +// The callback expects the key for the data in the `privdata` +// custom data parameter. +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + char *key = privdata; + + if (reply == NULL) { + if (c->errstr) { + printf("errstr: %s\n", c->errstr); + } + return; + } + + printf("Key: %s, value: %s\n", key, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + . + . + . + +// Key and string value to pass to `SET`. +char *key = "testkey"; +char *value = "testvalue"; + +// We aren't interested in the simple status reply for +// `SET`, so use NULL for the callback and custom data +// pointers. +redisAsyncCommand(c, NULL, NULL, "SET %s %s", key, value); + +// The reply from `GET` is essential, so set a callback +// to retrieve it. Also, pass the key to the callback +// as the custom data. +redisAsyncCommand(c, getCallback, key, "GET %s", key); +``` + +Note that you should normally disconnect asynchronously from a +callback when you have finished using the connection. +Use `redisAsyncDisconnect()` to disconnect gracefully, letting +pending commands execute and activate their callbacks. +Use `redisAsyncFree()` to disconnect immediately. If you do this then +any pending callbacks from commands that have already executed will be +called with a `NULL` reply pointer. + ## Command replies The information in the `redisReply` object has several formats,