赞
踩
本文创作基于
hiredis - v1.2.0版本
总之,hiredis是一个简单、高效的C语言客户端库,使开发人员可以轻松地与Redis进行交互。它适用于任何需要与Redis集成的C/C++项目,并且具有良好的性能和灵活性。
hiredis官网:https://redis.io/lp/hiredis/
hiredis github:https://github.com/redis/hiredis/releases
# 下载软件包,解压并移动目标位置 [root@Ali ~]# wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz [root@Ali ~]# tar xzvf v1.2.0.tar.gz [root@Ali ~]# mv hiredis-1.2.0 /usr/local/redis/hiredis # 安装依赖 [root@Ali hiredis]# yum install openssl-devel -y # 编译安装 hiredis [root@Ali hiredis]# make [root@Ali hiredis]# make install mkdir -p /usr/local/include/hiredis /usr/local/include/hiredis/adapters /usr/local/lib cp -pPR hiredis.h async.h read.h sds.h alloc.h sockcompat.h /usr/local/include/hiredis cp -pPR adapters/*.h /usr/local/include/hiredis/adapters cp -pPR libhiredis.so /usr/local/lib/libhiredis.so.1.1.0 cd /usr/local/lib && ln -sf libhiredis.so.1.1.0 libhiredis.so && ln -sf libhiredis.so.1.1.0 libhiredis.so.1 cp -pPR libhiredis.a /usr/local/lib mkdir -p /usr/local/lib/pkgconfig cp -pPR hiredis.pc /usr/local/lib/pkgconfig
可以看到,
make install
成功后:
- hiredis头文件 安装放在
/usr/local/include/hiredis
- 库文件放在
/usr/local/lib/
目录下adapters
注意
- 使用上述编译生成的so不支持
SSL
,若想支持SSL
需要再编译增加USE_SSL=1
参数,如:make USE_SSL=1 && make install USE_SSL=1
【参考 hiredis-README】
值得说明的是:hiredis
支持 同步
和 异步
两种调用方式。无论同步还是异步,使用hiredis
基本流程都是以下三个步骤:
redisConnect
连接数据库redisCommand
执行命令freeReplyObject
释放 redisReply
对象,使用 redisFree
来释放redisContext
。本小节以一个最简单的 同步API调用
示例,帮助读者建立API使用的整体过程。
一个最简单的API调用为例,至少需要引入以下几个函数:
/* 创建一个redis链接,并返回一个redis上下文 */
redisContext *redisConnect(const char *ip, int port);
/* 执行redis操作命令 */
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
/*释放资源*/
void freeReplyObject(void *reply);
void redisFree(redisContext *c);
示例代码:
#include <stdio.h> #include <string.h> #include <hiredis/hiredis.h> int main(){ redisReply *lpReply = nullptr; redisContext *lpContext = nullptr; /* 创建一个redis链接 */ lpContext = redisConnect("127.0.0.1", 6379); if (lpContext == NULL || lpContext->err) { if (lpContext) { printf("Error: %s\n", lpContext->errstr); // handle error } else { printf("Can't allocate redis context\n"); } } /* 执行redis操作命令 */ // void *redisCommand(redisContext *c, const char *format, ...); lpReply = (redisReply*)redisCommand(lpContext, "SET foo %s", "12345"); printf("type=%d, value=%d\n", lpReply->type, lpReply->integer); /* 释放一个响应对象 */ freeReplyObject(lpReply); lpReply = (redisReply*)redisCommand(lpContext, "GET foo"); printf("type=%d, value=%s\n", lpReply->type, lpReply->str); /* 释放一个响应对象 */ freeReplyObject(lpReply); /* 是否上下文 */ redisFree(lpContext); }
运行结果:
# 执行前,redis中不存在foo的key [wengjianhong@Ali testzone]$ redis-cli 127.0.0.1:6379> get foo (nil) 127.0.0.1:6379> # 编译运行 [wengjianhong@Ali testzone]$ g++ test_hiredis.cpp --std=c++11 -lhiredis -o test_hiredis [wengjianhong@Ali testzone]$ ./test_hiredis type=5, value=0 type=1, value=12345 # 执行后,获取 redis中foo的值 [wengjianhong@Ali testzone]$ redis-cli 127.0.0.1:6379> get foo "12345" 127.0.0.1:6379>
/* 建立连接 */ redisContext *redisConnect(const char *ip, int port); /* 带参数的建立连接 */ redisContext *redisConnectWithOptions(const redisOptions *options); /* 重连,重连时自动使用保存在上下文的参数 */ int redisReconnect(redisContext *c); /* 此外,redis还有如下的建立连接的接口,底层上也是调用 redisConnectWithOptions */ redisContext *redisConnectNonBlock(const char *ip, int port); redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr); redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectUnix(const char *path); redisContext *redisConnectFd(redisFD fd);
其中,调用建连接口返回redis上下文 redisContext
(注意: redisContext不是线程安全的
),其中保存hiredis的连接状态。
redisContext
中包含一个非零的整数err字段和一个带有错误描述的字符串字段。
/* Context for a connection to Redis */
typedef struct redisContext {
const redisContextFuncs *funcs; /* Function table */
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
// ... ...
} redisContext;
在使用redisConnect连接到Redis后,应该检查err字段,看看建立连接是否成功,如:
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
if (c) {
printf("Error: %s\n", c->errstr);
// handle error
} else {
printf("Can't allocate redis context\n");
}
}
注意:套接字选项直接应用于底层套接字,不会存储在 redisConnext
中,因此调用 redisReconnect
重连的时候,必须重新设置。如:
int redisEnableKeepAlive(redisContext *c);
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
hiredis支持多种向Redis发出操作指令的接口,函数原型如下:
/** * @brief 使用格式化字符串 * @param c redis上下文指针 * @param format 参数格式化字符串 * @param 参数 * @return redisReply 结构体指针 */ void* redisCommand(redisContext* c, const char* format, ...); /* 使用 argc、argv 参数列表 */ /** * @brief 使用 argc、argv 参数列表 * @param c redis上下文指针 * @param argc 参数个数 * @param argv 参数指针数组 * @param argvlen 参数长度数组 * @return redisReply 结构体指针 */ void* redisCommandArgv(redisContext* c, int argc, const char** argv, const size_t* argvlen);
上述的三个接口
void*
实际上是 redisReply*
结构体如下。注意:一旦返回错误,上下文就不能被重用,您应该建立一个新的连接。
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
size_t len; /* Length of string */
char* str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;
示例:
示例1
void* lpRelay = redisCommand(context, "SET foo bar");
void* lpRelay = redisCommand(context, "SET foo %s", value);
void* lpRelay = redisCommand(context, "SET %s %s", key, value);
void* lpRelay = redisCommand(context, "SET foo %b", value, (size_t) valuelen); /* 二进制参数 需要指定长度 */
/// 示例2
int argc = 3;
const char *argv[] = {"SET", "foo3", "bar3"};
size_t argvlen[] = {strlen(argv[0]), strlen(argv[1]), strlen(argv[2])};
void* lpReply = (redisReply*)redisCommandArgv(lpContext, argc, argv, argvlen);
hiredis
还支持管道的命令方式 和 显示的获取回复
/* Write a command to the output buffer. Use these functions in blocking mode
* to get a pipeline of commands. */
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
/* 获取返回结果 */
int redisGetReply(redisContext *c, void **reply);
int redisGetReplyFromReader(redisContext *c, void **reply);
示例:
// 示例1 redisReply *reply; redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"GET foo"); redisGetReply(context,(void**)&reply); // reply for SET freeReplyObject(reply); redisGetReply(context,(void**)&reply); // reply for GET freeReplyObject(reply); // 示例2 reply = redisCommand(context,"SUBSCRIBE foo"); freeReplyObject(reply); while(redisGetReply(context,(void *)&reply) == REDIS_OK) { // consume message freeReplyObject(reply); }
/* 释放一个响应对象 */
void freeReplyObject(void *reply)
/* 释放上下文(自动断开连接) */
void redisFree(redisContext *c);
调用异步建连接口返回redis异步上下文 redisAsyncContext
(注意: redisAsyncContext 不是线程安全的
)。此外,使用异步调用还需要设置回调函数。
/* 建立异步连接 */
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
/* 建立带参数选项的异步连接 */
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
/* 回调函数指针 */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);
typedef void(redisTimerCallback)(void *timer, void *privdata);
/* 设置异步回调函数 */
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
示例:
void appConnect(myAppData *appData) { redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { printf("Error: %s\n", c->errstr); // handle error redisAsyncFree(c); c = NULL; } else { appData->context = c; appData->connecting = 1; c->data = appData; /* store application pointer for the callbacks */ redisAsyncSetConnectCallback(c, appOnConnect); redisAsyncSetDisconnectCallback(c, appOnDisconnect); } } void appOnConnect(redisAsyncContext *c, int status) { myAppData *appData = (myAppData*)c->data; /* get my application specific context*/ appData->connecting = 0; if (status == REDIS_OK) { appData->connected = 1; } else { appData->connected = 0; appData->err = c->err; appData->context = NULL; /* avoid stale pointer when callback returns */ } appAttemptReconnect(); } void appOnDisconnect(redisAsyncContext *c, int status) { myAppData *appData = (myAppData*)c->data; /* get my application specific context*/ appData->connected = 0; appData->err = c->err; appData->context = NULL; /* avoid stale pointer when callback returns */ if (status == REDIS_OK) { appNotifyDisconnectCompleted(mydata); } else { appNotifyUnexpectedDisconnect(mydata); appAttemptReconnect(); } }
/* Reply回调函数 */
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
/* 异步发送操作命令 */
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);
注意:当命令执行完成发生 Reply回复回调时,回调数据只会在回调函数中有效,一旦回调函数结束指针指向的数据就无效了
/* 释放连接 */
void redisAsyncDisconnect(redisAsyncContext *ac);
/* 释放异步上下文 */
void redisAsyncFree(redisAsyncContext *ac);
参考:
最后忍不住想吐槽一下:C++的开源项目真的做的不如Python、java好。
Redis
作为当前最热门的开源项目之一,hiredis也有很多的用户。然而,hiredis的对外接口也没有很好的函数说明。
在使用过程中一个很大的问题,即:当传入的参数有多个时参数的含义基本靠猜(比如:redisCommandArgv
)、函数的返回值使用 void*
后很难知晓其含义(比如:redisCommand
)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。