将 hiredis 与 Qt 应用集成
将 hiredis
与 Qt 应用框架结合使用。
Qt 是一个流行的跨平台 C++ 框架,您可以使用它来构建命令行和 GUI 应用。本指南解释了如何使用 hiredis
从 Qt 应用连接到 Redis 服务器。
安装 Qt
如果您尚未这样做,您应该首先下载并安装适用于您的开发平台的 Qt 开发环境。下面的示例简要解释了如何使用 Qt Creator 来管理您的项目,但请参阅 Qt Creator 文档以获取大量的示例和教程。
创建一个简单的应用
我们将使用一个简单的控制台应用来演示如何从 Qt 连接到 Redis。使用 **文件 > 新建项目** 命令在 Qt Creator 中创建应用项目。生成的源代码是一个单独的 C++ 文件,名为 main.cpp
,它使用 QCoreApplication
对象来处理主事件循环。虽然它可以编译和运行,但在现阶段它并没有做任何有用的事情。
添加 hiredis
文件
如果您尚未这样做,请构建 hiredis
(有关更多信息,请参阅 构建和安装)。
您还应该使 libhiredis
库可用于该项目。例如,如果您已将 cmake
的默认选项用作项目构建工具,并且您已将 hiredis
的 .dylib
或 .so
文件安装在 /usr/local/lib
中,您应该将以下行添加到 CMakeLists.txt
文件中
add_library(hiredis SHARED IMPORTED)
set_property(TARGET hiredis PROPERTY
IMPORTED_LOCATION "/usr/local/lib/libhiredis.dylib")
您还应该修改 target_link_libraries
指令以包含 hiredis
target_link_libraries(ConsoleTest Qt${QT_VERSION_MAJOR}::Core hiredis)
添加代码以访问 Redis
您可以使用 Qt Creator 中项目文件夹上的 **添加新项** 上下文菜单添加类。以下各节给出了您应该添加到此类以连接到 Redis 的代码示例。该代码分为头文件和实现文件。
头文件
下面显示了一个名为 RedisExample
的类的头文件。代码后面会有一个解释。
// redisexample.h
#ifndef REDISEXAMPLE_H
#define REDISEXAMPLE_H
#include <QObject>
#include <hiredis/hiredis.h>
#include <hiredis/async.h>
#include <hiredis/adapters/qt.h>
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
是一个关键的 Qt 类,它实现了用于对象之间通信的 对象模型。当您在 Qt Creator 中创建类时,您可以指定您希望它成为 QObject
的子类(这将添加适当的头文件,并在类声明中包含 Q_OBJECT
宏)。
QObject
通信模型使用一些实例方法作为信号来报告事件,另一些实例方法作为槽来充当处理事件的回调(有关介绍,请参阅 信号和槽)。Qt 元对象编译器 识别类声明中的非标准 C++ 访问说明符 signals:
和 slots:
,并在编译期间为它们添加额外的代码以启用通信机制。
在我们的类中,有一个 run()
槽将实现访问 Redis 的代码。该代码最终会发出一个 finished()
信号,表明应用应该退出。
我们的简单示例代码只设置和获取一个 Redis 字符串 键。该类包含键和值的私有属性(遵循 Qt m_xxx
类成员命名约定)。这些由构造函数设置,并调用 QObject
构造函数。其他属性表示 Redis 的连接上下文(对于 Qt 应用,通常应该是异步)和一个 hiredis
用于与 Qt 集成的适配器对象。
实现文件
下面显示了实现头文件中声明的方法的文件。代码后面会有一个完整的解释。
// redisexample.cpp
#include <iostream>
#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<redisReply *>(r);
RedisExample *ex = static_cast<RedisExample *>(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);
}
访问 Redis 的代码位于 run()
方法中(回想一下,这实现了 Qt 槽,将在响应信号时调用)。该代码连接到 Redis,并将连接上下文指针存储在类实例的 m_ctx
属性中。调用 m_adapter.setContext()
会初始化上下文的 Qt 支持。请注意,我们需要 Qt 的异步连接。有关更多信息,请参阅 异步连接。
然后,该代码发出两个 Redis 命令,以 SET
使用该类的构造函数提供的字符串键和值。我们对该命令返回的响应不感兴趣,但我们对随后的 GET
命令的响应感兴趣。由于命令是异步的,我们需要设置一个回调来处理 GET
响应的到达。在 redisAsyncCommand()
调用中,我们将一个指向我们的 getCallback()
函数的指针,以及一个指向 RedisExample
实例的指针传递过去。这是一个自定义数据字段,它将简单地传递给回调函数执行(有关更多信息,请参阅 构造异步命令)。
getCallback()
函数中的代码首先将回复指针参数强制转换为 redisReply
,并将自定义数据指针强制转换为 RedisExample
。在这里,示例只是将回复字符串打印到控制台,但您可以以任何您喜欢的方式处理它。您可以将方法添加到您的类中,并使用在 redisAsyncCommand()
调用期间传递的自定义数据指针在回调中调用它们。在这里,我们只是使用指针来调用 finish()
方法。
finish()
方法调用 redisAsyncDisconnect()
来关闭连接,然后使用 Qt 信号机制发出 finished()
信号。您可能需要使用特定的连接上下文处理多个命令,但在完成使用后,应从回调中关闭它。
主程序
要访问 RedisExample
类,您应该在 main.cpp
中定义的 main()
函数中使用如下代码:
#include <QCoreApplication>
#include <QTimer>
#include "redisexample.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Instance of our object.
RedisExample r("url", "https://redis.ac.cn/");
// 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();
}
这会创建 QCoreApplication
实例,该实例管理控制台应用的主事件循环。 然后,它使用键 ("url") 和值 ("https://redis.ac.cn/") 为我们的 Redis 字符串创建 RedisExample
的实例。
下面的两行代码设置了应用程序的 QObject
通信机制。 调用 QTimer::singleShot()
会激活我们 RedisExample
实例上的 run()
槽方法。 QObject::connect()
调用会在 RedisExample
实例的 finished()
信号和我们的 QCoreApplication
实例的 quit()
槽之间创建一个通信链接。 当 RedisExample
对象发出 finished()
信号时,这将退出应用程序事件循环并退出应用程序。 这会在 GET
命令回调结束时调用 finish()
方法时发生。
运行代码
添加代码后,您可以从 Qt Creator 的构建菜单或窗口左侧的工具栏运行它。 假设与 Redis 的连接成功,它将打印消息 Value: https://redis.ac.cn/
并退出。 您可以使用 KEYS
命令从 redis-cli
或 Redis Insight 检查 “url” 字符串键是否已添加到 Redis 数据库中。
关键信息
您可以使用多种方法将 Redis 与 Qt 应用程序一起使用,但我们的示例演示了一些广泛适用的技术
- 使用
QObject
通信机制来简化您的代码。 - 使用
hiredis
异步 API。 向代码中添加RedisQtAdapter
实例,并确保在发出 Redis 命令之前调用其setContext()
方法对其进行初始化。 - 将所有与 Redis 交互所需的代码和数据(包括连接上下文)放在一个类中,或确保可以通过指针和 Qt 信号从一个类中访问它。 使用
redisAsyncCommand()
发出 Redis 命令时,在自定义数据参数中传递指向您的类的实例的指针,并使用它来处理回复或从回调中发出更多命令。