当前位置:   article > 正文

hiredis 源码安装和接口使用介绍_redis接口

redis接口

一、hiredis源码安装说明

本文创作基于 hiredis - v1.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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

可以看到,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 基本流程都是以下三个步骤:

  1. 使用 redisConnect 连接数据库
  2. 使用 redisCommand 执行命令
  3. 释放对象: 使用 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

示例代码:

#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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

运行结果:

# 执行前,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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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);


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

其中,调用建连接口返回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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在使用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");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:套接字选项直接应用于底层套接字,不会存储在 redisConnext 中,因此调用 redisReconnect 重连的时候,必须重新设置。如:

int redisEnableKeepAlive(redisContext *c);
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
  • 1
  • 2
  • 3

发送命令

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上述的三个接口

  • 执行成功时,返回值 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

示例:

 示例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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

示例:

// 示例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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

释放资源

/* 释放一个响应对象 */
void freeReplyObject(void *reply)

/* 释放上下文(自动断开连接) */
void redisFree(redisContext *c);
  • 1
  • 2
  • 3
  • 4
  • 5

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

示例:

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

发送命令

/* 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:当命令执行完成发生 Reply回复回调时,回调数据只会在回调函数中有效,一旦回调函数结束指针指向的数据就无效了

释放资源

/* 释放连接 */
void redisAsyncDisconnect(redisAsyncContext *ac);

/* 释放异步上下文 */
void redisAsyncFree(redisAsyncContext *ac);
  • 1
  • 2
  • 3
  • 4
  • 5

结束语

参考:

最后忍不住想吐槽一下:C++的开源项目真的做的不如Python、java好。
Redis作为当前最热门的开源项目之一,hiredis也有很多的用户。然而,hiredis的对外接口也没有很好的函数说明。
在使用过程中一个很大的问题,即:当传入的参数有多个时参数的含义基本靠猜(比如:redisCommandArgv)、函数的返回值使用 void* 后很难知晓其含义(比如:redisCommand

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/592462
推荐阅读
相关标签
  

闽ICP备14008679号