一、hiredis源码安装说明
本文创作基于
hiredisv1.2.0版本
1.简介
- hiredis是一个用于与Redis交互的C语言客户端库。它提供了一组简单易用的API,使开发人员可以轻松地连接到Redis服务器,并执行各种操作,如设置和获取键值对、执行命令、订阅和发布消息等。
- hiredis的设计目标是高效性和简单性。它使用纯C语言编写,没有外部依赖,可以轻松地与任何C/C++项目集成。它具有轻量级的实现和低延迟的性能,适用于高并发的应用场景。
- hiredis支持同步和异步的方式与Redis进行通信。同步方式是指客户端发送一个命令后会一直等待Redis的响应,直到响应返回后才继续执行下一个命令。异步方式是指客户端发送命令后可以继续执行其他任务,通过回调函数来处理Redis的响应。
- 除了基本的Redis操作,hiredis还提供了一些高级功能,如管道操作和事务。管道操作允许一次性发送多个命令到Redis,以减少网络开销。事务可以将一系列命令打包成一个原子操作,保证它们的执行是连续的。
总之,hiredis是一个简单、高效的C语言客户端库,使开发人员可以轻松地与Redis进行交互。它适用于任何需要与Redis集成的C/C++项目,并且具有良好的性能和灵活性。
2.下载源码
hiredis官网:https://redis.io/lp/hiredis/
hiredis github:https://github.com/redis/hiredis/releases
3.安装说明
# 下载软件包,解压并移动目标位置
[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
支持 同步
和 异步
两种调用方式。无论同步还是异步,使用hiredis
基本流程都是以下三个步骤:
- 使用
redisConnect
连接数据库 - 使用
redisCommand
执行命令 - 释放对象: 使用
freeReplyObject
释放redisReply
对象,使用redisFree
来释放redisContext
。
1. 简单示例
本小节以一个最简单的 同步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>
2.同步API说明
建立链接
/* 建立连接 */
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*
结构体如下。 - 执行失败时,返回值为NULL,并且将设置上下文中的err字段(请参阅错误部分)。
注意:一旦返回错误,上下文就不能被重用,您应该建立一个新的连接。
/* 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);
3.异步API说明
建立连接
调用异步建连接口返回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);
结束语
参考:
- GitHub上的hiredis项目的 README文档
最后忍不住想吐槽一下:C++的开源项目真的做的不如Python、java好。
Redis
作为当前最热门的开源项目之一,hiredis也有很多的用户。然而,hiredis的对外接口也没有很好的函数说明。
在使用过程中一个很大的问题,即:当传入的参数有多个时参数的含义基本靠猜(比如:redisCommandArgv
)、函数的返回值使用 void*
后很难知晓其含义(比如:redisCommand
)