Linux下的c/c++开发之操作Redis数据库
C/C++ 操作 Redis 的常用库
在 C/C++ 开发中操作 Redis 有多种方式,最主流的选择是使用第三方客户端库。由于 Redis 官方本身是使用 C 编写的,提供的 API 非常适合 C/C++ 调用。常见的 Redis C/C++ 客户端库包括:
-
hiredis
:官方推荐的轻量级 C 客户端。 -
hiredis-vip
:支持 Redis Cluster 的增强版 hiredis。 -
redis-plus-plus
:基于 hiredis 的现代 C++ 封装,使用更简洁直观。
hiredis 简介(官方推荐,轻量高效)
hiredis
是 Redis 官方维护的 C 语言客户端库,专注于提供最基本的 Redis 通信支持。它设计简洁,仅包含:
-
同步命令发送与接收
-
简单的异步接口(需配合 libevent、libev 使用)
-
轻量高效、易于嵌入项目中
核心特性
-
同步与异步 API 支持
-
占用内存小,编译快速
-
无多余封装,紧贴 Redis 协议
-
社区活跃,文档简洁
hiredis-vip 简介(支持 Redis Cluster)
hiredis-vip
是在 hiredis 基础上增强的版本,专门用于支持 Redis Cluster 的自动分片与节点路由功能。它由开源社区维护,并兼容 hiredis 的接口风格。
核心特性
-
支持 Redis Cluster 的自动路由与重定向处理
-
封装了 key-slot 映射、MOVED/ASK 重试等逻辑
-
同样提供同步和异步模式
-
提供
redisClusterContext
和集群级命令发送接口
redis-plus-plus
(又名 sw::redis++
)是基于 hiredis
的现代 C++ 封装库,由中国开发者 swz30 编写。它使用 STL 风格设计,提供 RAII、异常处理、泛型接口,更符合现代 C++ 开发习惯。
核心特性
-
提供类模板支持多种数据类型(如
std::string
,std::vector
) -
支持连接池、管道(pipeline)、事务(transaction)
-
封装 cluster 支持(底层依赖 hiredis-vip)
-
易用且文档完善,适合快速上手
该篇文章主要介绍hiredis-vip的安装和使用
hiredis-vip的安装
必要依赖
hiredis-vip
依赖官方 hiredis
作为底层 Redis 通信库,因此需要先安装 hiredis
:
git clone https://github.com/redis/hiredis.git
cd hiredis
make
sudo make install
从github拉取源码编译安装hiredis-vip
git clone https://github.com/vipshop/hiredis-vip.git
cd hiredis-vip
make
sudo make install
安装后我们会得到:
①静态库文件:/usr/local/lib/libhiredis-vip.a 这是你在 C/C++ 项目中需要链接的库。
②头文件:
头文件路径 | 主要功能描述 | 提供的核心类型/函数 | 使用场景 | 是否必需 |
---|---|---|---|---|
<hiredis-vip/hiredis.h> | 单机版 Redis 客户端接口(继承自官方 hiredis) | redisContext 、redisConnect() 、redisCommand() | 单个 Redis 实例(非集群) | 若用单机模式需要 |
<hiredis-vip/hircluster.h> | Redis Cluster 操作的核心接口 | redisClusterContext 、redisClusterCommand() | 分布式 Redis 集群通信 | 集群操作需要 |
<hiredis-vip/adapters/libevent.h> | 异步 Redis Cluster 支持,绑定到 libevent 事件循环 | redisClusterLibeventAttach() 等 | 异步事件驱动开发(libevent) | 仅异步需要 |
<hiredis.h>介绍
结构体
redisContext
用于连接上下文
struct redisContext {int fd; // 套接字描述符int flags; // 状态标志char *errstr; // 错误描述字符串int err; // 错误码void *reader; // 协议解析器
};
结构体redisReply
所有 Redis 响应的封装结构体
typedef struct redisReply {int type; // 响应类型,对应 REDIS_REPLY_* 枚举(如 REDIS_REPLY_STRING, REDIS_REPLY_INTEGER 等)long long integer; // 如果 type == REDIS_REPLY_INTEGER,则该字段表示返回的整数值int len; // 如果 type == REDIS_REPLY_STRING 或 REDIS_REPLY_STATUS 或 REDIS_REPLY_ERROR,则该字段表示 str 的长度(不包含 '\0')char *str; // // - 当 type == REDIS_REPLY_STRING 时,str 指向字符串值// - 当 type == REDIS_REPLY_ERROR 时,str 是错误信息// - 当 type == REDIS_REPLY_STATUS 时,str 是状态信息(如 "OK")// - 其他类型该字段为 NULLsize_t elements; // 如果 type == REDIS_REPLY_ARRAY,则表示数组中的元素数量struct redisReply **element; // // - 当 type == REDIS_REPLY_ARRAY 时,该字段指向一个 redisReply* 数组,// 每个元素是数组中的一个返回项(可递归地是另一个 redisReply)// - 其他类型该字段为 NULL
} redisReply;
结构体 redisAsyncContext
用于表示一个异步 Redis 客户端上下文
typedef struct redisAsyncContext {redisContext c; // 内部使用的同步上下文(基础连接信息)int err; // 错误码char errstr[128]; // 错误信息字符串void *data; // 用户数据...
} redisAsyncContext;
redisReply中的类型枚举值
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
值 | 含义 |
---|---|
REDIS_REPLY_STRING | 字符串类型 |
REDIS_REPLY_ARRAY | 数组结果(如 KEYS * ) |
REDIS_REPLY_INTEGER | 整数类型(如 INCR 结果) |
REDIS_REPLY_NIL | 空值(如不存在的 key) |
REDIS_REPLY_STATUS | 状态,如 "OK" |
REDIS_REPLY_ERROR | 错误 |
API
①redisConnect
连接 Redis 服务器
redisContext *redisConnect(const char *ip, int port);
-
ip
:Redis 服务器 IP 地址,如"127.0.0.1"
-
port
:端口号,如6379
-
返回:成功返回
redisContext*
,失败err
字段非零,errstr
描述错误。
②redisConnectWithTimeout
带超时设置的连接方式
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
tv
:超时时间(秒+微秒),如 {1, 500000}
表示 1.5 秒。
③redisFree
释放连接资源
void redisFree(redisContext *c);
参数:Redis 连接上下文。
④redisCommand
发送命令并同步获取结果
void *redisCommand(redisContext *c, const char *format, ...);
-
format
:格式化字符串,如"SET key %s"
、"INCR %s"
; -
可变参数:要插入的 key、value 等。
-
返回:指向
redisReply
的指针,需使用freeReplyObject
释放。
redisReply *reply = (redisReply *)redisCommand(c, "SET %s %s", "mykey", "hello");
⑤freeReplyObject
释放 redisReply
内存
void freeReplyObject(void *reply);
⑥redisCommandArgv
使用参数数组方式发送命令
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
-
argc
:参数数量; -
argv
:参数数组; -
argvlen
:每个参数的长度数组; -
返回:
redisReply*
。
const char *set_argv[] = {"SET", "mykey", "hello"};
size_t set_argvlen[] = {3, 5, 5}; // 每个参数的长度
redisReply *reply = (redisReply *)redisCommandArgv(c, 3, set_argv, set_argvlen);
示例:
#include <iostream>
#include <string>
#include <hiredis-vip/hiredis.h>int main() {// 1. 创建连接redisContext* c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {std::cerr << "Connection error: " << (c ? c->errstr : "NULL") << std::endl;return -1;}// 2. 使用 redisCommand 发送 SET 命令redisReply* reply = (redisReply*)redisCommand(c, "SET %s %s", "foo", "123");if (reply && reply->type == REDIS_REPLY_STATUS) {std::cout << "SET result: " << reply->str << std::endl;}freeReplyObject(reply);// 3. 使用 redisCommand 发送 GET 命令reply = (redisReply*)redisCommand(c, "GET %s", "foo");if (reply && reply->type == REDIS_REPLY_STRING) {std::cout << "GET result: " << reply->str << std::endl;}freeReplyObject(reply);// 4. 使用 redisCommandArgv 发送 DEL 命令const char* argv[] = {"DEL", "foo"};size_t argvlen[] = {3, 3};reply = (redisReply*)redisCommandArgv(c, 2, argv, argvlen);if (reply && reply->type == REDIS_REPLY_INTEGER) {std::cout << "DEL result: " << reply->integer << std::endl;}freeReplyObject(reply);// 5. 释放连接redisFree(c);return 0;
}
⑦异步相关api
函数原型 | 函数作用 | 函数参数 | 返回值 |
---|---|---|---|
redisClusterAsyncConnect(const char *startup_nodes, int flags) | 异步连接 Redis Cluster | - startup_nodes :Redis Cluster 节点的启动地址列表,格式如 "127.0.0.1:7000" 。 - flags :连接选项标志。 | 返回 redisClusterAsyncContext* ,表示异步上下文。 |
redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn) | 设置连接成功的回调函数 | - acc :异步上下文。 - fn :连接成功回调函数,参数为 redisClusterAsyncContext* acc 和 redisContext* c 。 | 无返回值 |
redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn) | 设置连接断开的回调函数 | - acc :异步上下文。 - fn :断开连接回调函数,参数为 redisClusterAsyncContext* acc 和 redisContext* c 。 | 无返回值 |
redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...) | 异步发送格式化命令 | - acc :异步上下文。 - fn :命令执行完后的回调函数,参数为 redisClusterAsyncContext* acc 和 redisReply* r 。 - privdata :回调函数的用户数据。 - format :命令的格式化字符串。 | 返回 void* ,通常传入回调函数。 |
redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) | 异步参数数组方式发送命令 | - acc :异步上下文。 - fn :命令执行完后的回调函数,参数为 redisClusterAsyncContext* acc 和 redisReply* r 。 - privdata :回调函数的用户数据。 - argc :参数数量。 - argv :命令的参数数组。 - argvlen :每个参数的长度。 | 返回 void* ,通常传入回调函数。 |
redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) | 将异步 Redis Cluster 上下文绑定到 libevent | - acc :异步上下文。 - base :libevent 事件循环的基础对象。 | 返回 int ,成功为 0,失败为 -1。 |
redisClusterAsyncFree(redisClusterAsyncContext *acc) | 释放异步上下文 | - acc :异步上下文。 | 无返回值 |
示例:
#include <iostream>
#include <hiredis-vip/hircluster.h>
#include <event.h>
#include <cstring>// 连接成功的回调
void connectCallback(redisClusterAsyncContext *acc, redisContext *c) {std::cout << "Connected to Redis Cluster!" << std::endl;
}// 断开连接的回调
void disconnectCallback(redisClusterAsyncContext *acc, redisContext *c) {std::cout << "Disconnected from Redis Cluster!" << std::endl;
}// 异步命令的回调
void commandCallback(redisClusterAsyncContext *acc, redisReply *reply) {if (reply->type == REDIS_REPLY_STRING) {std::cout << "Reply: " << reply->str << std::endl;} else if (reply->type == REDIS_REPLY_STATUS) {std::cout << "Status: " << reply->str << std::endl;} else {std::cout << "Received unexpected reply type" << std::endl;}freeReplyObject(reply);
}int main() {// 创建 libevent 事件基础对象struct event_base *base = event_base_new();if (!base) {std::cerr << "Unable to create event base!" << std::endl;return -1;}// 异步连接 Redis ClusterredisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:7000", 0);if (acc == NULL || acc->err) {std::cerr << "Connection error: " << (acc ? acc->errstr : "NULL") << std::endl;return -1;}// 设置连接和断开回调redisClusterAsyncSetConnectCallback(acc, connectCallback);redisClusterAsyncSetDisconnectCallback(acc, disconnectCallback);// 将异步上下文与 libevent 绑定if (redisClusterLibeventAttach(acc, base) != 0) {std::cerr << "Failed to attach to libevent!" << std::endl;redisClusterAsyncFree(acc);event_base_free(base);return -1;}// 发送 SET 命令redisClusterAsyncCommand(acc, commandCallback, NULL, "SET %s %s", "foo", "bar");// 发送 GET 命令redisClusterAsyncCommand(acc, commandCallback, NULL, "GET %s", "foo");// 发送带有参数数组的命令(例如 MSET)const char *argv[] = {"MSET", "foo", "bar", "baz", "qux"};size_t argvlen[] = {4, 3, 3, 3, 3};redisClusterAsyncCommandArgv(acc, commandCallback, NULL, 5, argv, argvlen);// 运行事件循环,等待命令执行event_base_dispatch(base);// 释放资源redisClusterAsyncFree(acc);event_base_free(base);return 0;
}
<hircluster.h>介绍
结构体redisClusterContext
-
redisClusterContext
是整个 Redis 集群交互的核心结构,管理了集群拓扑(节点和 slot 映射)、连接信息、重定向机制等。 -
使用该结构的 API(如
redisClusterCommand
)时,内部会根据 key 自动决定使用哪个节点连接并完成请求。
typedef struct redisClusterContext {int err; // 错误码,0 表示无错误,非 0 表示出错char errstr[128]; // 错误信息字符串,用于描述出错原因int flags; // 标志位,预留扩展,如是否启用 pipeline 等// 当前操作所使用的连接节点信息redisContext *con; // 当前正在使用的 hiredis 连接上下文(非集群结构,但内部已支持)char *cmd; // 上一次执行的命令字符串副本(用于调试或日志)struct dict *nodes; // 所有节点信息(key 为 ip:port,value 为 node 对象)struct dict *slots; // slot 到节点的映射表(0-16383)struct list *requests; // pipeline 请求列表(若使用 pipeline 模式)struct timeval *timeout; // 连接超时设置char *err_code; // 附加的 Redis 错误码字符串(如 MOVED, ASK)..........
} redisClusterContext;
结构体redisClusterAsyncContext
相比普通 Redis,异步集群客户端使用 redisClusterAsyncContext
typedef struct redisClusterAsyncContext {redisClusterContext *cc; // 关联的集群上下文void *data;...
} redisClusterAsyncContext;
API
①redisClusterConnect
创建一个 Redis Cluster 上下文并初始化连接信息。
redisClusterContext *redisClusterConnect(const char *nodes, int flags);
参数:
-
nodes
:Redis 节点地址字符串,如"127.0.0.1:7000,127.0.0.1:7001"
,支持多个地址。 -
flags
:连接选项,目前应传0
,为保留字段。
返回值:
-
成功返回
redisClusterContext*
-
失败返回
NULL
,可通过cc->err
和cc->errstr
获取错误信息。
②redisClusterSetOptionAddNode
动态添加一个集群节点地址到上下文中,适用于构建连接前动态配置节点。
"动态添加" 指的是在Redis 集群连接已经创建并且正在使用的过程中,后续可以增加新的节点地址到已经存在的 redisClusterContext
中,而无需重新创建或重新连接整个集群。
int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv);
参数:
-
cc
:Redis 集群上下文 -
addr
:如"127.0.0.1:7002"
返回值:
-
成功返回 0,失败返回 -1
③redisClusterSetOptionConnectTimeout
设置连接超时时间。
int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv);
参数:
-
cc
:Redis 集群上下文 -
tv
:struct timeval
结构体,单位为秒和微秒(例如{2, 0}
表示 2 秒)
返回值:
-
成功返回 0,失败返回 -1
处该设置外,其他常用设置有:
函数名称 | 函数原型 | 作用说明 |
---|---|---|
设置连接超时时间 | int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, struct timeval tv); | 设置连接 Redis 节点的最大时间,超过即失败(单位:秒+微秒) |
设置操作超时时间 | int redisClusterSetOptionTimeout(redisClusterContext *cc, struct timeval tv); | 设置执行 Redis 命令的超时时间 |
设置读取响应超时时间 | int redisClusterSetOptionReadTimeout(redisClusterContext *cc, struct timeval tv); | 设置等待 Redis 响应数据的最大时间 |
设置最大重试次数 | int redisClusterSetOptionMaxRetries(redisClusterContext *cc, int max_retries); | 出现网络/重定向等错误时最大重试次数 |
设置最大重定向次数 | int redisClusterSetOptionMaxRedirects(redisClusterContext *cc, int max_redirects); | 设置因 Moved/Ask 错误进行的最多重定向次数 |
设置错误处理策略 | int redisClusterSetOptionErrHandling(redisClusterContext *cc, int on_error); | 设置遇到错误时的处理策略(重试/中断等) |
设置认证密码 | int redisClusterSetOptionPassword(redisClusterContext *cc, const char *password); | 设置连接 Redis 节点的密码(适用于集群开启密码认证的情况) |
启用 SSL 加密 | int redisClusterSetOptionSsl(redisClusterContext *cc, int ssl_flag); | 设置是否使用 SSL 连接 Redis(1 开启,0 关闭) |
添加初始节点地址 | int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *ipport); | 添加用于初始化连接的节点地址,支持多个 |
④redisClusterConnect2
实际建立 Redis Cluster 的连接,必须在设置完选项后调用。
int redisClusterConnect2(redisClusterContext *cc);
返回值:
-
成功返回 0,失败返回 -1
⑤redisClusterCommand
向 Redis Cluster 发送格式化命令(如 SET %s %s
),自动路由到正确的节点。
在 Redis Cluster 模式中,所有的键(key)会被映射到 0~16383 共 16384 个槽(slots)中,每个槽再由集群中的某个节点负责。例如:
-
节点 A 管理 slot 0~5460
-
节点 B 管理 slot 5461~10922
-
节点 C 管理 slot 10923~16383
当你执行命令 SET mykey 123
时:
-
Redis 会对
mykey
做 CRC16 哈希并对 16384 取模,得到一个 slot 编号(例如slot 9181
) -
然后 Redis Cluster 会根据 slot 映射表,找到哪个节点负责这个 slot
-
然后把命令发送到对应的节点去执行
hiredis-vip 如何“自动路由”?
hiredis-vip
提供了 redisClusterCommand()
函数,它会:
-
自动对 key 做哈希并映射到 slot
-
根据 slot,查找当前负责这个 slot 的节点
-
自动建立连接(或复用已有连接)并将命令发送给这个节点
-
返回对应的
redisReply*
给你,无需你关心命令该发给哪个 IP/端口
你只需写:
redisClusterCommand(cc, "SET %s %s", "mykey", "hello");
hiredis-vip 会自动完成这些步骤,无需你手动计算哈希、选节点。
⑥redisClusterCommandArgv
以参数数组形式向 Redis Cluster 发送命令,适用于包含二进制数据的场景。
void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
参数:
-
cc
:Redis 集群上下文 -
argc
:参数个数 -
argv
:参数数组,如{"SET", "key", "value"}
-
argvlen
:每个参数的长度数组
返回值:
-
返回
redisReply*
指针,需手动freeReplyObject()
释放
⑦redisClusterFree
释放上下文资源,避免内存泄露。
void redisClusterFree(redisClusterContext *cc);
⑧redisClusterReset
重置上下文信息,清空连接和缓存,用于恢复连接失败后的重建。
void redisClusterReset(redisClusterContext *cc);
redis连接的两种方式:
方式一:简洁连接(推荐,自动 connect)
redisClusterContext *cc = redisClusterConnect("127.0.0.1:7000", 0);
这个函数会:
-
创建
redisClusterContext
-
设置初始节点列表
-
自动调用
redisClusterConnect2()
进行实际连接
所以:如果你用了 redisClusterConnect()
,就不需要自己手动调用 redisClusterConnect2()
。
方式二:手动配置 + 显式连接
这种方式适用于你需要更精细控制连接参数的场景:
redisClusterContext *cc = redisClusterContextInit(); // 只创建上下文
redisClusterSetOptionAddNodes(cc, "127.0.0.1:7000"); // 添加节点
redisClusterSetOptionConnectTimeout(cc, timeout); // 设置连接超时等选项
redisClusterConnect2(cc); // 必须手动调用连接
在使用 redisClusterContextInit()
+ 设置选项方式时,你必须手动调用 redisClusterConnect2()
,否则上下文未建立连接,后续命令会失败。
示例:
⑨异步相关api
函数原型 | 功能说明 | 函数参数 | 返回值 |
---|---|---|---|
redisClusterAsyncConnect(const char *startup_nodes, int flags) | 异步连接 Redis Cluster | - startup_nodes :Redis Cluster 节点的启动地址列表,格式如 "127.0.0.1:7000" 。 - flags :连接选项标志。 | 返回 redisClusterAsyncContext* ,表示异步上下文。 |
redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn) | 设置连接成功的回调函数 | - acc :异步上下文。 - fn :连接成功回调函数,参数为 redisClusterAsyncContext* acc 和 redisContext* c 。 | 无返回值 |
redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn) | 设置连接断开的回调函数 | - acc :异步上下文。 - fn :断开连接回调函数,参数为 redisClusterAsyncContext* acc 和 redisContext* c 。 | 无返回值 |
redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...) | 异步发送格式化命令 | - acc :异步上下文。 - fn :命令执行完后的回调函数,参数为 redisClusterAsyncContext* acc 和 redisReply* r 。 - privdata :回调函数的用户数据。 - format :命令的格式化字符串。 | 返回 void* ,通常传入回调函数。 |
redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) | 异步参数数组方式发送命令 | - acc :异步上下文。 - fn :命令执行完后的回调函数,参数为 redisClusterAsyncContext* acc 和 redisReply* r 。 - privdata :回调函数的用户数据。 - argc :参数数量。 - argv :命令的参数数组。 - argvlen :每个参数的长度。 | 返回 void* ,通常传入回调函数。 |
redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) | 将异步 Redis Cluster 上下文绑定到 libevent | - acc :异步上下文。 - base :libevent 事件循环的基础对象。 | 返回 int ,成功为 0,失败为 -1。 |
redisClusterAsyncFree(redisClusterAsyncContext *acc) | 释放异步上下文 | - acc :异步上下文。 | 无返回值 |
示例:
#include <iostream>
#include <hiredis-vip/hircluster.h>
#include <event2/event.h>// 回调函数:连接成功
void connectCallback(redisClusterAsyncContext *acc, redisContext *c) {std::cout << "Connected to Redis Cluster!" << std::endl;
}// 回调函数:连接断开
void disconnectCallback(redisClusterAsyncContext *acc, redisContext *c) {std::cout << "Disconnected from Redis Cluster." << std::endl;
}// 回调函数:命令响应
void commandCallback(redisClusterAsyncContext *acc, redisReply *r) {if (r != NULL) {std::cout << "Command reply: " << r->str << std::endl;} else {std::cout << "Command failed" << std::endl;}
}// 主函数
int main() {// 初始化 libevent 事件循环struct event_base *base = event_base_new();if (!base) {std::cerr << "Could not initialize libevent!" << std::endl;return -1;}// 异步连接 Redis Clusterconst char *startup_nodes = "127.0.0.1:7000,127.0.0.1:7001";redisClusterAsyncContext *acc = redisClusterAsyncConnect(startup_nodes, 0);if (acc == NULL || acc->err) {std::cerr << "Connection error: " << (acc ? acc->errstr : "NULL") << std::endl;event_base_free(base);return -1;}// 设置连接成功的回调redisClusterAsyncSetConnectCallback(acc, connectCallback);// 设置连接断开的回调redisClusterAsyncSetDisconnectCallback(acc, disconnectCallback);// 将异步上下文与 libevent 事件循环绑定if (redisClusterLibeventAttach(acc, base) != 0) {std::cerr << "Failed to attach to libevent!" << std::endl;redisClusterAsyncFree(acc);event_base_free(base);return -1;}// 异步发送 SET 命令redisClusterAsyncCommand(acc, commandCallback, NULL, "SET %s %s", "foo", "bar");// 异步发送 GET 命令redisClusterAsyncCommand(acc, commandCallback, NULL, "GET %s", "foo");// 启动事件循环event_base_dispatch(base);// 释放资源redisClusterAsyncFree(acc);event_base_free(base);return 0;
}
<libevent.h>
<hiredis-vip/adapters/libevent.h>是 hiredis-vip
提供的 Libevent 适配器模块,用于将异步 Redis/Redis Cluster 客户端集成进基于 libevent
的事件驱动程序中,实现非阻塞式通信。
-
hiredis-vip
支持 异步模式:你发命令出去,不会等 Redis 响应,你继续干别的事。响应来了,它会回调你,告诉你结果。 -
但是这时候需要一个人帮你“看着 socket”,当有数据能读、能写了时,提醒你去处理一下 , 这个人就是
libevent
。 -
所以你要把
hiredis-vip
的 Redis 客户端和libevent
建立联系:你告诉libevent
:你帮我盯着 hiredis 的 socket,有事了通知我。 -
hiredis-vip/adapters/libevent.h
就是负责搭桥的,让这两者能配合起来完成这一切。
在高性能网络服务中,比如使用 libevent
写的 HTTP 服务或消息系统,你不想因为一个 Redis 命令就阻塞整个线程,你希望:
-
我发一个 SET 命令 → Redis 那边处理它 → 回来后你再告诉我结果(回调函数)
-
我整个服务的主线程仍然在跑,不停响应其他请求
-
这种模式就必须用 非阻塞 I/O + 事件驱动 + 回调机制
这正是 libevent + hiredis-vip async
的组合实现的。
-
hiredis 提供了
redisAsyncContext
:可以用它异步发送命令 + 设置回调处理响应。 -
但它自己不处理 socket 的可读/可写通知,你要告诉一个 event loop 去“监听 socket fd”
-
libevent
是个 event loop 框架,它可以event_add()
来监听 socket 的各种事件 -
所以
redisLibeventAttach()
就是把两者连接起来,把 redisAsyncContext 的 socket 交给 libevent 管 -
以后 Redis 响应来了,libevent 会帮你触发你之前设置的回调函数
API
redisLibeventAttach
将 Redis 上下文(无论是同步的 redisContext
还是异步的 redisAsyncContext
)与 libevent
事件循环绑定,使得 Redis 能够使用 libevent
进行非阻塞的 I/O 操作。
int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base);
-
参数:
-
c
:指向 异步Redis 上下文的指针=。 -
base
:libevent
事件循环的基础对象,通常是通过event_base_new()
创建。
-
-
返回值:
-
成功时返回
0
。 -
失败时返回
-1
。
-
示例:
#include <iostream>
#include <hiredis-vip/hiredis.h>
#include <hiredis-vip/adapters/libevent.h>
#include <event.h>// 回调函数:处理 Redis 响应
void reply_callback(redisAsyncContext *c, void *r, void *privdata) {redisReply *reply = (redisReply *)r;if (reply == nullptr) {std::cerr << "Error: " << c->errstr << std::endl;return;}std::cout << "Redis reply: " << reply->str << std::endl;
}int main() {// 1. 创建 libevent 事件基础对象struct event_base *base = event_base_new();if (!base) {std::cerr << "Error creating event base." << std::endl;return -1;}// 2. 创建异步 Redis 上下文并连接 Redis 服务器redisAsyncContext *ac = redisAsyncConnect("127.0.0.1", 6379);if (ac == nullptr || ac->err) {std::cerr << "Connection error: " << (ac ? ac->errstr : "Unknown error") << std::endl;return -1;}// 3. 将异步 Redis 上下文绑定到 libevent 事件循环if (redisLibeventAttach(ac, base) != 0) {std::cerr << "Error attaching Redis context to libevent." << std::endl;return -1;}// 4. 发送一个 Redis 命令并设置回调函数redisAsyncCommand(ac, reply_callback, nullptr, "SET %s %s", "key", "value");// 5. 启动事件循环,开始处理异步事件event_base_dispatch(base);// 6. 清理redisAsyncFree(ac);event_base_free(base);return 0;
}
相关文章:
Linux下的c/c++开发之操作Redis数据库
C/C 操作 Redis 的常用库 在 C/C 开发中操作 Redis 有多种方式,最主流的选择是使用第三方客户端库。由于 Redis 官方本身是使用 C 编写的,提供的 API 非常适合 C/C 调用。常见的 Redis C/C 客户端库包括: hiredis:官方推荐的轻量…...
通过SMTP协议实现Linux邮件发送配置指南
一、环境准备与基础配置 1. SMTP服务开通(以qq邮箱为例) 登录qq邮箱网页端,进入「设置」-「POP3/SMTP/IMAP」 开启「SMTP服务」并获取16位授权码(替代邮箱密码使用) 记录关键参数: SMTP服务器地址&#…...
数学复习笔记 8
前言 成为一个没有感情的刷题机器就可以变得很强了。 逆矩阵的运算 随便算一下就算出来了,没啥难的。主要是用天然可交换的矩阵来算。有三个天然可交换的矩阵,某矩阵和单位阵,该矩阵和它的伴随矩阵,该矩阵和它的逆矩阵。一定要…...
【证书与信任机制】自签名证书的风险与适用场景
关于 自签名证书的风险与适用场景 的详细解析,以及在内网测试中安全使用自签名证书的实践指南: 一、自签名证书的核心风险 1. 信任缺失与安全警告 • 浏览器/操作系统不信任:自签名证书未被权威CA签发,客户端默认视为“不安全”&a…...
[python] Python单例模式:__new__与线程安全解析
一 实例的创建过程 我们之前了解过在构造一个类的实例化对象时,会默认调用__init__方法,也就是类的初始化也叫构造函数,但其实在调用__init__方法前会首先调用__new__方法(只有在py3新式类才有)。即下面 __new__(): 创建实例 作…...
【SSL证书系列】操作系统如何保障根证书的有效性和安全
操作系统通过多层次的安全机制和技术手段保障根证书的有效性和安全性,防止篡改、伪造或滥用。以下是核心保障措施: 1. 根证书的存储与隔离 • 受保护的存储区域 根证书存储在操作系统的受信任根证书存储区(如Windows的“受信任的根证书颁发机…...
毕业论文,如何区分研究内容和研究方法?
这个问题问得太好了!😎 “研究内容”和“研究方法”经常被初学者(甚至一些老油条)混淆,尤其写论文开题报告时,一不小心就“内容”和“方法”全混在一块儿,连导师都看懵。 今天就来给大家一文讲…...
《Effective Python》第2章 字符串和切片操作——深入理解 Python 中 __repr__ 与 __str__
引言 本文基于学习《Effective Python》第三版 Chapter 2: Strings and Slicing 中的 Item 12: Understand the Difference Between repr and str When Printing Objects 后的总结与延伸。在 Python 中,__repr__ 和 __str__ 是两个与对象打印密切相关的魔术方法&am…...
C及C++的SOAP协议库
一.gSOAP gSOAP 是一个功能强大的开源工具包,专为 C 和 C 设计,用于快速开发基于 SOAP 协议的 Web 服务和客户端。 1.协议支持 SOAP 版本:完整支持 SOAP 1.1/1.2 规范,包括消息格式、编码规则和错误处理。 传输协议:…...
推荐一个Winform开源的UI工具包
从零学习构建一个完整的系统 推荐一个开源、免费的适合.NET WinForms 控件的套件。 项目简介 Krypton是一套开源的.Net组件,用于快速构建具有丰富UI交互的WinForms应用程序。 丰富的UI控件,提供了48个基础控件,如按钮、文本框、标签、下拉…...
【Linux内核】内存管理之虚拟内存详解
目录 一、Linux 虚拟内存概述 二、虚拟内存的基本概念 1. 地址空间 2. 页表 3. 页面 三、虚拟内存的管理机制 1. 页面分配与回收 2. 页面置换 3. 内存映射 四、虚拟内存的保护机制 1. 访问权限 2. 写时复制 五、虚拟内存的优化技术 1. 大页 2. 透明大页 3. 内存…...
upload-labs通关笔记-第5关 文件上传之.ini绕过
目录 一、ini文件绕过原理 二、源码审计 三、渗透实战 1、查看提示 2、制作.user.ini文件 (1)首先创建一个文本文件 (2)保存文件名为.user.ini 2、制作jpg后缀脚本 (1)创建一个文本文件 …...
入门OpenTelemetry——部署OpenTelemetry
OpenTelemetry 部署模式 OpenTelemetry Collector 按部署方式分为 Agent 和Gateway 模式。 Agent 模式 在 Agent 模式下,OpenTelemetry 检测的应用程序将数据发送到与应用程序一起驻留的(收集器)代理。然后,该代理程序将接管并…...
构建现代化WPF应用:数据驱动开发与高级特性解析
启动时默认打开哪个界面是在App.xaml的StartupUri属性中设置。 Window标签 x:Class起到了部分类的作用,让XAML与CS文件做关联,起到了映射的作用。 xmlns是XML NameSpace的简称,起到了命名控件的作用,与代码中命名空间不同的是&a…...
MATLAB实现振幅调制(AM调制信号)
AM调制是通信专业非常重要的一个知识点。今天我们使用MATLAB编程实现AM调制。 我们实现输入一个载波信号的频率与调制信号的频率后,再输入调幅度,得到已调信号的波形与包络信号的波形,再使用FFT算法分析出已调信号的频谱图。 源代码&#x…...
loss = -F.log_softmax(logits[:, -1, :], dim=1)[0, irrational_id]
loss = -F.log_softmax(logits[:, -1, :], dim=1)[0, irrational_id] 解释这行代码 loss = -F.log_softmax(logits[:, -1, :], dim=1)[0, irrational_id] 的具体含义和背后的数学原理。 1. 代码拆解与功能 这行代码的核心是计算负对数似然损失(Negative Log Likelihood Los…...
NSSCTF [HNCTF 2022 WEEK4]
题解前的吐槽:紧拖慢拖还是在前段时间开始学了堆的UAF(虽然栈还没学明白,都好难[擦汗]),一直觉得学的懵懵懂懂,不太敢发题解,这题算是入堆题后一段时间的学习成果,有什么问题各位师傅可以提出来,…...
基于springboot+vue的医院门诊管理系统
开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7数据库工具:Navicat12开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9 系统展示 系统登录 系统首…...
大模型越狱:技术漏洞与安全挑战——从原理到防御
近年来,随着大模型能力的飞速提升,其安全性问题日益受到关注。其中,“大模型越狱”(Model Jailbreaking)成为热议焦点——指通过特定手段绕过模型的安全限制,诱导其生成有害、违法或超出设计范围的内容。本…...
Linux 常用命令 -hostnamectl【主机名控制】
简介 hostnamectl 命令中的 “hostname” 顾名思义,指的是计算机在网络上的名称,“ctl” 是 “control” 的缩写,意味着控制。hostnamectl 命令用于查询和修改系统主机名以及相关的设置。它通过与 systemd 系统管理器交互,允许用…...
《Python星球日记》 第72天:问答系统与信息检索
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、问答系统概述1.问答系统的工作原理2. 问答系统的典型应用场景 二、问答系统…...
VUE3 -综合实践(Mock+Axios+ElementPlus)
目录 前言 目标 1.工程创建 2.Mock 2.1 配置Mock 扩 展 2.2 定义模拟数据 2.3 创建Mock服务器 3.导入ElementPlus 4.表格页面搭建 5.动态路由跳转 6.详情页面的制作 前言 基于前文 VUE3详细入门,我们对VUE3的基本使用有了初步的了解,下…...
Qt原型模式实现与应用
在Qt中实现原型模式(Prototype Pattern)可以通过以下步骤完成。该模式的核心是通过克隆现有对象来创建新对象,而非通过传统的构造函数。以下是详细说明和示例: 1. 原型模式的核心概念 目的:避免重复初始化对象的高成本…...
语音识别-2
上一篇关于语音识别, 虽然能用,但在系统适配,机器适配方面,速度,性能等还是有优化的地方.所以这篇是关于这些的. 1.蓝牙优化 A2DP:是一种单向的高品质音频数据传输链路,通常用于播放立体声音乐;SCO: 则是一种双向的音频数据的传输链路,该链路只支持8K及16K单声道的音频数据…...
ElasticSearch深入解析(十二):聚合——分桶聚合、指标聚合、管道子聚合
文章目录 一、分桶聚合1. 分桶聚合的核心逻辑与核心类型2. 分桶聚合的高级特性 二、指标聚合1. 指标聚合的核心逻辑与基础类型(1)基础统计指标(单值输出)(2)复合统计指标(多值输出) …...
互联网大厂Java求职面试:AI内容生成平台下的高并发架构设计与性能优化
互联网大厂Java求职面试:AI内容生成平台下的高并发架构设计与性能优化 场景背景: 郑薪苦是一名经验丰富的Java开发者,他正在参加一家匿名互联网大厂的技术总监面试。这家公司专注于基于AI的内容生成平台,支持大规模用户请求和复杂…...
论MCU如何在Fatfs中使用Flash接口的方法
前提: MCU移植了FS,如FATFSOSFMount工具 OSFMount或者其他磁盘工具用于挂载.img镜像,可格式化文件系统打开并放入实际使用的文件 步骤 1. cmd命令建立空.img镜像,以下为12MB fsutil file createnew fat.img 120000002. OSFMo…...
Python+Selenium爬虫:豆瓣登录反反爬策略解析
1. 引言 在当今互联网时代,数据抓取(爬虫)技术广泛应用于数据分析、市场调研、自动化测试等领域。然而,许多网站采用动态加载技术(如Ajax、React、Vue.js等框架)来渲染页面,传统的**<font s…...
nt!MiDispatchFault函数分析之第一次循环前后的变化
第一部分:nt!MiDispatchFault函数分析之第一次循环之前 1: kd> !pte 0x002bf810 VA 002bf810 PDE at C0300000 PTE at C0000AFC contains 7B314867 contains 00000000 pfn 7b314 ---DA--UWEV not valid 1: kd> dd C0000AFC…...
JMeter性能测试工具使用
JMeter是一款强大的性能测试工具,由Java编写,小巧轻便,最关键的是开源免费,现在已经成了主流的性能测试工具。 下面介绍一下基本的安装使用、高级功能及可视化实时图表展示,带你们感受一下JMeter的世界~ 1、安装 1.…...
Windows 环境下安装 Node 和 npm
安装 Node.js 和 npm https://nodejs.org/zh-cn/download 执行 fnm install 22 之后,执行 node 或 npm 提示找不到命令 fnm env 看环境变量 找到 node 和 npm 命令在 C:\Users\HUAWEI\AppData\Roaming\fnm\node-versions\v22.15.0\installation 目录下࿰…...
开发指南112-样式的优先级别
在前端样式设置里,界面元素一般会多个地方进行定义和影响。一般而言,CSS样式的优先级如下: 1、内联样式:style属性中定义的样式,具有最高的优先级。 2、ID选择器:通过ID选择器指定的样式ÿ…...
单向通信机制EventSource
EventSource 是浏览器提供的一种实现服务器推送 简称 SSE 基于 HTTP 协议的单向通信机制 可以通过服务器将实时数据推送到客户端 而不需要客户端不断发起请求EventSource 和 WebSocket 都可以实现服务器向客户端的实时数据推送,但它们有不同的适用场景:E…...
PyTorch中mean(dim=1)的深度解析
mean(dim=1) 是什么意思 在自然语言处理中,文本经过分词器处理后会转换为token序列,每个token对应一个向量表示。mean(dim=1) 的作用是在序列维度上对这些向量取平均,将整个序列压缩为单个向量。下面我用具体例子解释: 1. 张量的维度结构 假设我们有一个输入文本:"…...
Xcode报错:“Set `maskView` to `nil` before adding it as a subview of ZFMaskView
Assertion failure in -[ZFMaskView _addSubview:positioned:relativeTo:] 嗯,坑爹的IOS18,当你基于UIView实现的自己的子类中定义一个属性并初始化时就会出现崩溃! /// 遮罩property (nonatomic, strong) UIView *maskView; 因为UIVIEW本…...
uniapp -- 验证码倒计时按钮组件
jia-countdown-verify 验证码倒计时按钮组件 一个用于发送短信验证码的倒计时按钮组件,支持自定义样式、倒计时时间和文本内容。适用于各种需要验证码功能的表单场景。 代码已经 发布到插件市场 可以自行下载 下载地址 特性 支持自定义按钮样式(颜色、…...
e.g. ‘django.db.models.BigAutoField‘.
在Django框架中,django.db.models.BigAutoField 是一个用于数据库模型的字段类型,它用于自动增长的ID字段。这个字段类型特别适用于需要处理大量数据的应用,比如在大型网站或应用中,普通的 AutoField 可能不足以存储增长的ID值&am…...
【HTTPS基础概念与原理】对称加密与非对称加密在HTTPS中的协作
在HTTPS通信中,对称加密和非对称加密协同工作,共同保障数据的机密性和密钥交换的安全性。以下是两者的协作机制及RSA、ECDHE等算法的核心作用: 一、对称加密与非对称加密的分工 1. 对称加密(如AES、ChaCha20) • 作用&…...
ESP系列单片机选择指南:结合实际场景的最优选择方案
前言 在物联网(IoT)快速发展的今天,ESP系列单片机凭借其优异的无线连接能力和丰富的功能特性,已成为智能家居、智慧农业、工业自动化等领域的首选方案。本文将深入分析各款ESP芯片的特点,结合典型应用场景,帮助开发者做出最优选择…...
使用Thrust库实现异步操作与回调函数
使用Thrust库实现异步操作与回调函数 在Thrust库中,你可以通过CUDA流(stream)来实现异步操作,并在适当的位置插入回调函数。以下是如何实现的详细说明: 基本异步操作 Thrust本身并不直接暴露CUDA流接口,但你可以通过以下方式使…...
【Python 异常处理】
Python 的异常处理机制是构建健壮程序的核心工具,通过 try-except 结构实现优雅的错误管理。以下是系统化指南: 一、基础异常处理结构 try:# 可能出错的代码result 10 / 0 except ZeroDivisionError:# 异常处理逻辑print("错误:除数不…...
40:相机与镜头选型
第一章 相机 1.1 理论基础 1.1.1 相机分类 1 )按照芯片类型: CCD 相机、 CMOS 相机 2 )按照传感器的结构特性:线阵相机、面阵相机 3 )按照扫描方式:隔行扫描相机、逐行扫描相机 4 )按…...
【ESP32-S3】Guru Meditation Error 崩溃分析实战:使用 addr2line 工具 + bat 脚本自动解析 Backtrace
【ESP32-S3】Guru Meditation Error 崩溃分析实战:使用 addr2line 工具 bat 脚本自动解析 Backtrace 在使用 ESP32-S3 进行开发时,我们常常遇到串口报错,例如: Guru Meditation Error: Core 1 paniced (LoadProhibited). Exce…...
win11 VSCode 强制弹窗微软登录
今天在一台新电脑上配置VSCode同步的时候,用了微软账号,因为这台电脑比较特殊,不方便科学上网,所以一开始用的微软账户登录,导致和GitHub账号登录的配置、扩展等等不同步。 后面准备改用GitHub账号登录发现不行&#…...
Thrust库中的Gather和Scatter操作
Thrust库中的Gather和Scatter操作 Thrust是CUDA提供的一个类似于C STL的并行算法库,其中包含两个重要的数据操作:gather(聚集)和scatter(散开)。 Gather操作 Gather操作从一个源数组中按照指定的索引收集元素到目标数组中。 函数原型: t…...
springboot + mysql8降低版本到 mysql5.7
springboot mysql8降低版本到 mysql5.7 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency>spring:datasource:driverClassName: com.mysql.jdbc.D…...
在 Windows 中配置使用 WSL 来运行 Linux 环境,主要有以下步骤:
一、安装 WSL 方法一:使用 wsl --install 命令 :以管理员身份运行 PowerShell 或 Windows 命令提示符,输入 wsl --install 命令,该命令将自动启用运行 WSL 所需的功能,并安装默认的 Linux 发行版 Ubuntu。 方法二&…...
TypeScript泛型:从入门到精通的全方位指南
TypeScript泛型:从入门到精通的全方位指南 前言 最近在社区看到很多小伙伴对TypeScript的泛型概念感到困惑。作为一名从Java转战前端的老兵,我想用最接地气的方式,带大家彻底搞懂这个看似高深实则简单的重要特性。 一、什么是泛型…...
DA14531如何在固件中生成与时间相关的mac和版本号
一. 蓝牙device information service显示固件编译时间 这里我是用序列号的characteristic来显示。 只需要把序列号的characteristic value用宏的方式: #define APP_DIS_SERIAL_NB_STR (__DATE__" "__TIME__) #define APP_DIS_SERIAL_NB_STR…...
内存安全设计方案
内存安全设计方案 1. 内存问题分析 1.1 常见内存问题 内存泄漏: 单例对象持有过多引用缓存未及时清理线程池资源未释放内存溢出: 大对象频繁创建队列积压并发处理不当GC频繁: 对象创建过多内存碎片化大对象分配1.2 问题场景 #mermaid-svg-uPgkoDPv6GNGT28v {font-family:&…...