目录
思考一下redis编程的整个过程。 我们作为redis客户端。需要跟redis服务器交互。
封装 Redis 的 C++ 类的过程可以分为以下几个步骤:
思考一下redis编程的整个过程。 我们作为redis客户端。需要跟redis服务器交互。
那说白了还是服务器应用开发的那一套逻辑。
1. 建立连接
2. 通信交互 (命令执行command, 服务器响应reply) 不要忘记资源回收
3. 销毁连接
封装 Redis 的 C++ 类的过程可以分为以下几个步骤:
定义 Redis 类:
首先,你需要定义一个 C++ 类来表示 Redis 连接和操作。
这个类通常包含连接 Redis 服务器、执行命令、处理返回结果等功能。
一个完成发布订阅功能的 Redis 类
成员变量:
Redis 服务器地址和端口:用于连接 Redis 服务器。
Redis 连接上下文:用于管理与 Redis 服务器的连接。
订阅消息处理函数:用于处理接收到的订阅消息。
功能函数:
连接函数:用于与 Redis 服务器建立连接。
订阅函数:用于向 Redis 订阅一个或多个频道,并设置订阅消息处理函数。
取消订阅函数:用于取消订阅一个或多个频道。
发布函数:用于向指定频道发布消息。
- redisContext
管理与Redis服务器连接的上下文对象, 包含fd、flags、tcp连接信息等参数
redisContext 对象是 hiredis 库中与 Redis 服务器通信的核心对象,
它封装了连接的状态信息以及与服务器通信的相关参数。
在使用 hiredis 库时,需要先创建一个 redisContext 对象,
并使用它来执行 Redis 命令,当操作完成后,需要手动释放该对象占用的资源
typedef struct redisContext {
int err; // 错误码,0 表示没有错误
char errstr[128]; // 错误信息字符串
int fd; // 与 Redis 服务器的连接套接字描述符
int flags; // 连接标志
struct timeval timeout; // 超时时间
struct sockaddr_in *tcp; // TCP 连接信息
} redisContext;
- redisReply
存储 Redis 服务器响应的数据
当使用 hiredis 库中的 redisReply 结构体时,
它表示了 Redis 服务器的响应,包含了响应的类型、整数值、字符串值、数组元素等信息。
在使用完毕后,需要调用 freeReplyObject() 函数释放内存,以避免内存泄漏。
typedef struct redisReply {
int type; // Redis 响应类型,如字符串、整数、数组等
long long integer; // 整数值,仅在 type 为 REDIS_REPLY_INTEGER 时有效
size_t len; // 字符串长度或数组元素个数
char *str; // 字符串值,仅在 type 为 REDIS_REPLY_STRING、REDIS_REPLY_STATUS 和 REDIS_REPLY_ERROR 时有效
size_t elements; // 数组元素个数,仅在 type 为 REDIS_REPLY_ARRAY 时有效
struct redisReply **element; // 数组元素指针数组,仅在 type 为 REDIS_REPLY_ARRAY 时有效
} redisReply;
redisContext *redisConnect(const char *ip, int port);
redisConnect返回值都是靠redisContext连接上下文来接收的 eg: m_publish_context, m_subscribe_context,之后就通过操作redisContext对象来完成与Redis服务器的交互过程。redisContext相当于是Redis服务器的句柄
void *redisCommand(redisContext *c, const char *format, ...);
redisCommand 专门用来执行redis命令的
redisReply: This is the reply object returned by redisCommand()
freeReplyObject(reply); 执行完之后需要将reply释放掉, 避免内存泄漏
redisAppendCommand 就像是把命令写在一张待发的清单上,然后继续去做其他事情,
而 redisBufferWrite 就是把这张清单上的所有命令一起发出去,
然后等待所有命令执行完毕,并把结果收集起来。
使用这两个函数结合起来的方式,可以提高程序执行 Redis 命令的效率,
特别是在需要执行大量命令或者需要异步执行命令的情况下。
-
redisAppendCommand:
redisAppendCommand
函数用于向 Redis 服务器发送一条 Redis 命令,但不立即等待命令执行结果。相反,它会将命令放入 hiredis 内部的输出缓冲区中,并立即返回。这使得程序可以继续执行其他操作,而不必等待命令执行完成。- 使用
redisAppendCommand
可以实现异步执行 Redis 命令的功能,适用于需要同时执行多个命令或者与其他操作并行执行的场景。
-
redisBufferWrite:
redisBufferWrite
函数用于将输出缓冲区中的 Redis 命令发送到 Redis 服务器,并等待服务器的响应。它会阻塞当前线程,直到所有待发送的命令都已经发送完成,并且等待所有命令的执行结果返回。- 当调用
redisBufferWrite
函数时,hiredis 库会将输出缓冲区中的所有待发送命令一次性发送到 Redis 服务器。然后,它会等待所有命令执行完成,并将执行结果存储在相应的回复对象中,以便程序后续处理。
#ifndef REDIS_H
#define REDIS_H
#include <hiredis/hiredis.h>
#include <thread>
#include <functional>
using namespace std;
class Redis
{
public:
Redis();
~Redis();
// 连接redis服务器
bool connect();
// 向redis指定的通道channel发布消息
bool publish(int channel, string message);
// 向redis指定的通道subscribe订阅消息
bool subscribe(int channel);
// 向redis指定的通道unsubscribe取消订阅消息
bool unsubscribe(int channel);
// 在独立线程中接收订阅通道中的消息
void observer_channel_message();
// 初始化向业务层上报通道消息的回调对象
void init_notify_handler(function<void(int, string)> fn);
private:
// hiredis同步上下文对象,负责publish消息
redisContext *_publish_context;
// hiredis同步上下文对象,负责subscribe消息
redisContext *_subcribe_context;
// 回调操作,收到订阅的消息,给service层上报
function<void(int, string)> _notify_message_handler;
};
#endif
class Redis { // 定义redis类
public:
Redis(const char* host, int port)
: m_host(host), m_port(port)
, m_publish_context(NULL)
, m_subscribe_context(NULL)
, m_subscribeCallback(NULL)
{
connect();
}
~Redis() {
disconnect();
}
bool connect(); // 对于redisConnect的接口封装, 返回消息上下文.
void disconnect() { // 释放连接, 归还redis服务器系统资源
if (m_context) {
redisFree(m_context);
m_context = NULL;
}
}
// 向redis指定的通道channel发布消息
bool publish(int channel, std::string message);{
// reply 拿到 redisCommand在Redis服务器上的执行结果/返回值/响应
redisReply *reply =
(redisReply *)redisCommand(m_publish_context, "PUBLISH %d %s", channel, message.c_str());
if (nullptr == reply) 做出出错处理;
freeReplyObject(reply);
}
// 向redis指定的通道subscribe订阅消息
bool subscribe(int channel) {
if (REDIS_ERR == redisAppendCommand(m_subcribe_context, "SUBSCRIBE %d", channel)) {
std::cerr << "subscribe command failed" << std::endl;
return false;
}
// redisBufferWrite可以循环发送缓冲区, 直到缓冲区数据发送完毕(done被置为1)
int done = 0;
while (!done) {
if (REDIS_ERR == redisBufferWrite(m_subcribe_context, &done)) {
std::cerr << "subscribe command failed" << std::endl;
return false;
}
}
// redisGetReply
return true;
}
// 取消订阅消息, 和subscribe函数实现几乎一毛一样, 除了命令变成UNSUBSCRIBE
bool Redis::unsubscribe(int channel);
// 初始化向业务层上报通道消息的回调对象
// set_notify_message_handler
void init_notify_handler(function<void(int, string)> fn);
// 在独立线程中接收订阅通道中的消息
void Redis::observer_channel_message()
{
redisReply *reply = nullptr;
while (REDIS_OK == redisGetReply(m_subcribe_context, (void **)&reply))
{
// 订阅收到的消息是一个带三元素的数组
if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr)
{
// 给业务层上报通道上发生的消息
_notify_message_handler(atoi(reply->element[1]->str) , reply->element[2]->str);
}
freeReplyObject(reply);
}
cerr << ">>>>>>>>>>>>> observer_channel_message quit <<<<<<<<<<<<<" << endl;
}
private:
// ip + port
// hiredis同步上下文对象, 负责publish消息
redisContext *m_publish_context; // 发布消息上下文
// hiredis同步上下文对象, 负责subscribe消息
redisContext *m_subcribe_context; // 订阅消息上下文
// 回调操作, 收到订阅的消息, 给service层上报, 订阅消息处理回调函数
function<void(int, string)> m_notify_message_handler;
};