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

news2025/1/23 21:22:04

一、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

可以看到,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);

示例代码:

#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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1681360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#知识|上位机子窗体嵌入主窗体方法(实例)

哈喽,你好啊,我是雷工! 上位机开发中,经常会需要将子窗体嵌入到主窗体, 本节练习C#中在主窗体的某个容器中打开子窗体的方法。 01 需求说明 本节练习将【账号管理】子窗体在主窗体的panelMain容器中打开。 账号管理子窗体如下: 主窗体的panelMain容器位置如图: 02 实现…

电子合同怎么盖章的

数字证书盖章&#xff1a;利用个人或企业的数字证书进行盖章。数字证书作为数字身份证明&#xff0c;确保了电子签名和盖章的可信度。通过加密技术&#xff0c;确保合同内容不被篡改&#xff0c;盖章过程完成后&#xff0c;合同具有法律效力。 时间戳盖章&#xff1a;在电子合…

【神经网络与深度学习】Transformer原理

transformer ENCODER 输入部分 对拆分后的语句x [batch_size, seq_len]进行以下操作 Embedding 将离散的输入&#xff08;如单词索引或其他类别特征&#xff09;转换为稠密的实数向量&#xff0c;以便可以在神经网络中使用。位置编码 与RNN相比&#xff0c;RNN是一个字一个字…

C++进阶之路:何为默认构造函数与析构函数(类与对象_中篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Xilinx 7系列FPGA的时钟管理

在7系列FPGA中&#xff0c;时钟管理单元&#xff08;CMT&#xff09;包含了混合模式时钟管理器&#xff08;MMCM&#xff09;和锁相环&#xff08;PLL&#xff09;。PLL是包含了MMCM功能的一个子集。CMT骨干网可用于链接CMT的时钟功能。CMT图&#xff08;图3-1&#xff09;展示…

数字功放-改善液晶显示屏音频性能,重塑音频体验

随着液晶电视、液晶显示器以及等离子电视屏幕的尺寸不断增大&#xff0c;音频性能要求相应提高&#xff1b;数字功放芯片作为音频解决方案&#xff1b;不仅为音频设备带来更高的效率和更低的功耗&#xff0c;同时在显示屏上进一步提高了平板显示器的音质&#xff0c;使之具有了…

位运算概述

首先 位运算这个东西在考试中十分容易考&#xff0c;所以要多多看一看位运算的相关知识&#xff0c;多刷一刷题之类的。 位运算的概念 位运算就是二进制数据进行运算的运算符。 注意&#xff1a;通常我们用二进制补码来表示&#xff0c;补码的符号位也是要参与运算的。 通常的…

PostMan 测试

创建一个集合管理测试接口 token获取 Tests&#xff1a;后置脚本 Api 请求后的操作&#xff0c;一般写断言脚本的地方 Pre-request Script &#xff1a;后置脚本 请求前的操作 以下代码放进Tests 后置脚本当中。 var respObj JSON.parse(responseBody); // 获取到TOK…

C语言之旅:动态内存管理

目录 一.为什么要有动态内存分配 二.malloc和free 2.1 malloc 2.2 free 2. 3malloc和free的使用 三. calloc 四. raelloc 4.1 代码示例&#xff1a; 4.2 注意事项&#xff1a; 4.3 对动态开辟空间的越界访问 4.4 对非动态开辟内存使⽤free释放 4.5 使用free释放⼀块…

分布式系统的一致性与共识算法(四)

Etcd与Raft算法 Raft保证读请求Linearizability的方法: 1.Leader把每次读请求作为一条日志记录&#xff0c;以日志复制的形式提交&#xff0c;并应用到状态机后&#xff0c;读取状态机中的数据返回(一次RTT、一次磁盘写)2.使用Leader Lease&#xff0c;保证整个集群只有一个L…

STM32手写寄存器的方式实现点亮LED灯

这次是从头开始学习STM32&#xff0c;看野火的视频开始学习&#xff0c;感觉需要记录的时候就要记录一下学习的心得。野火视频学习的老师讲的还是很到位的&#xff0c;能够学习到很多的细节之处&#xff0c;有时会感觉很啰嗦&#xff0c;但是不得不说确实很详细&#xff0c;只有…

IT行业现状与探索未来发展趋势

​​​​​​​ 我眼中的IT行业现状与未来趋势 随着技术的不断进步&#xff0c;IT行业已成为推动全球经济和社会发展的关键力量。从云计算、大数据、人工智能到物联网、5G通信和区块链&#xff0c;这些技术正在重塑我们的生活和工作方式。你眼中IT行业的现状及未来发展趋势是…

六西格玛绿带培训:解锁质量工程师的职场新篇章

在质量管理这条道路上&#xff0c;我们或许都曾有过这样的疑问&#xff1a;为何付出了同样的努力&#xff0c;却未能获得预期的回报&#xff1f;当我们看到身边的同行们逐渐步入高薪的行列&#xff0c;而自己却似乎陷入了职业的泥沼&#xff0c;这种对比无疑令人倍感焦虑。然而…

iRemovalPro完美解4G信号,支持A12+,支持6S~14ProMax,支持iOS17.4+

iRemovalPro是一款绕过激活锁界面的解锁工具&#xff0c;可以激活所有iPhone/ipad恢复信号&#xff0c;并且支持插卡接打电话、收发短信、4G流量上网&#xff0c;支持iCloud登录&#xff0c;有消息通知&#xff0c;支持iPhone6S~14ProMax的所有型号&#xff0c;支持iOS15-iOS17…

酷开科技的智能电视操作系统—酷开系统,带来更加舒适的观看体验

酷开科技的智能电视操作系统——酷开系统&#xff0c;通过大数据和人工智能技术的结合&#xff0c;会根据会员的观看历史和收视行为偏好&#xff0c;刻画出“消费者群体画像”&#xff0c;然后将内容进行“人工编辑智能推荐”的方式推送到消费者面前&#xff0c;不仅省去了消费…

僵尸网络的威胁值得关注

僵尸网络&#xff08;botnet&#xff09;是指一组受到恶意软件感染并遭到恶意用户控制的计算机。术语“僵尸网络”由“机器人&#xff08;bot&#xff09;”和“网络&#xff08;network&#xff09;”两个词组合而成&#xff0c;每台受感染设备被称为“机器人”。僵尸网络可用…

谷歌举办Gemini API开发者大赛;ChatGPT iOS版更新支持中文

&#x1f989; AI新闻 &#x1f680; 谷歌举办Gemini API开发者大赛&#xff0c;大奖1981款电动DeLorean 摘要&#xff1a;IT之家 5 月 15 日消息&#xff0c;在 2024 年谷歌 I/O 开发者大会上&#xff0c;谷歌宣布举办 Gemini API 开发者大赛&#xff0c;主要面向个人开发者…

Springboot+Vue项目-基于Java+MySQL的高校专业实习管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

Spring MVC(建立连接 + 请求)

文章目录 一、建立客户端和服务器的连接二、如何构造请求&#xff08;传参&#xff09;2.1 构造请求方式 参数通用注解2.2 传递单个参数2.3 传递多个参数2.4 传递数组/集合2.5 传递对象2.6 传递JSON 三、相关的其他请求操作3.1 获取URL中的参数 PathVariable3.2 上传文件 Requ…

Golang | Leetcode Golang题解之第92题反转链表II

题目&#xff1a; 题解&#xff1a; func reverseBetween(head *ListNode, left, right int) *ListNode {// 设置 dummyNode 是这一类问题的一般做法dummyNode : &ListNode{Val: -1}dummyNode.Next headpre : dummyNodefor i : 0; i < left-1; i {pre pre.Next}cur :…