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

一、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 基本流程都是以下三个步骤:

  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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

绝地求生:29.2商城更新内容预览:挣脱尘网通行证,经典皮肤返场,空投活动

就在今天历经9小时维护&#xff0c;29.2版本终于上线&#xff0c;柠檬茶带大家一起看看&#xff0c;这次游戏里都更新了哪些内容吧。 挣脱尘网通行证 豪华版&#xff1a;$14.99 普通版&#xff1a;$4.99 豪华版比普通版多10级升级券和2套生存者宝箱 分支一 分支二 分支三 额外…

我21岁玩“撸货”,被骗1000多万

最近&#xff0c;撸货业界内发生了一些颇受瞩目的事件。 在郑州&#xff0c;数码档口下面抢手团长跑路失联&#xff0c;涉及金额几百万&#xff0c;在南京&#xff0c;一家知名的电商平台下的收货站点突然失联&#xff0c;涉及金额高达一千多万&#xff0c;令众多交易者震惊不已…

【oracle】图片转为字节、base64编码等形式批量插入oracle数据库并查询

1.熟悉、梳理、总结下Oracle相关知识体系 2.欢迎批评指正&#xff0c;跪谢一键三连&#xff01; 资源下载&#xff1a; oci.dll、oraocci11.dll、oraociei11.dll3个资源文件资源下载&#xff1a; Instant Client Setup.exe资源下载&#xff1a; oci.dll、oraocci11.dll、oraoc…

CTFshow 爆破

第一题0 抓包发现输入账号密码后数据包会多一个base64编码 所有推出用户密码在传输的时候进行了加密 所以爆破时也用base64加密 设置paylod 用题目给的字典 在添加两条规则 第一个是增加前缀 admin: 第二个是使得payload进行base64编码 取消URL编码字符 因为会把号编码了 开…

【多模态】31、Qwen-VL | 一个开源的全能的视觉-语言多模态大模型

文章目录 一、背景二、方法2.1 模型架构2.2 输入和输出2.3 训练 三、效果3.1 Image Caption 和 General Visual Question Answering3.2 Text-oriented Visual Question Answering3.3 Refer Expression Comprehension3.4 视觉-语言任务的少样本学习3.5 真实世界用户行为中的指令…

【FPGA、maltab】基于FPGA的SOQPSK调制解调技术的设计与实现

基于FPGA的SOQPSK调制解调技术的设计与实现 SOQPSK一、QPSK、OQPSK、SOQPSK之间的关系二、SOQPSK调制原理 matlab 仿真FPGA 实现顶层设计发射模块接收模块顶层调制解调FPGA代码 SOQPSK 一、QPSK、OQPSK、SOQPSK之间的关系 SOQPSK&#xff08;Shaped Offset Quadrature Phase …

Oracle 自治数据库 Select AI 初体验

这几天有点时间&#xff0c;准备尝试下Oracle Select AI&#xff0c;虽然此功能2023年就已经发布了。 Oracle自治数据库已经集成好了Select AI&#xff0c;本文也是讲的这个。 配置 Select AI 需要以下步骤&#xff1a; 创建ADB申请Cohere/OpenAI免费账号设置ADB测试Select…

第十七篇:数据库性能优化的数学视角:理论与实践的融合

数据库性能优化的数学视角&#xff1a;理论与实践的融合 1. 引言 在现代信息技术快速发展的背景下&#xff0c;数据库性能优化已经成为计算机科学领域的一个热点问题。随着数据量的爆炸式增长和用户需求的多样化&#xff0c;数据库系统所承载的数据处理任务变得越来越复杂&…

unity制作app(7)--panel control

根据用户的状态&#xff0c;在界面中显示不同的panel 1.新建一个脚本PanelControl&#xff0c;控制各个脚本的显示与隐藏 2.实现第一个逻辑判断功能&#xff1a;如果没有登记过信息&#xff0c;就直接跳转到登记界面&#xff0c;如果登记过&#xff0c;跳转到住界面。许多需要…

2A 150KHz 40V Buck DC to DC 转换器XL1509

前言&#xff1a; 该器件仅做介绍&#xff0c;不推荐在新设计中使用。 新设计应尽量使用MHZ开关频率&#xff0c;以降低电感量&#xff0c;从而降低成本。 新设计应使用同步DCDC降压转换器。 XL1509丝印和封装 引脚定义 XL1509管脚描述 管脚编号 管脚名称 管脚描述 1 电压输入…

Online RL + IL :Policy Improvement via Imitation of Multiple Oracles

NIPS 2020 paper code 如何利用多个次优专家策略来引导智能体在线学习&#xff0c;后续有多个文章研究该设定下的RL。 Intro 论文探讨了在强化学习&#xff08;RL&#xff09;中&#xff0c;如何通过模仿多个次优策略&#xff08;称为oracle&#xff09;来提升策略性能的问题…

宁静致远(“静”)

宁静致远是一个成语&#xff0c;读音为nng jng zh yuǎn&#xff0c;意思是只有心境平稳沉着、专心致志&#xff0c;才能厚积薄发、 有所作为。出自《淮南子:主术训》。 出处 宁静致远张铭篆刻 此句最早出自西汉初年道家刘安的《淮南子:主术训》&#xff0c;蜀汉丞相诸葛亮的…

TEMU电商行情分析:未来趋势与盈利机遇探讨

近年来&#xff0c;跨境电商行业风起云涌&#xff0c;其中TEMU作为新兴力量&#xff0c;其市场表现备受关注。那么&#xff0c;TEMU电商现在的行情究竟如何?对于卖家而言&#xff0c;是否仍然是一个能够赚钱的平台呢? 首先&#xff0c;从市场趋势来看 TEMU电商正处于一个快速…

创建按钮的第二种方法

可以设置两个参数&#xff0c;按钮的内容和父对象 QPushButton * button2 new QPushButton("第二个按钮",this); 区别&#xff1a; 方式1&#xff1a;窗口默认大小&#xff0c;按钮显示在左上角 方式2&#xff1a;窗口是根据按钮的大小来创建的 (所以需要重置窗…

Java入门基础学习笔记27——生成随机数

Random的使用&#xff1a;生成随机数。 随机数应用&#xff1a; 随机点名&#xff1a; 年会抽奖&#xff1a; 猜数字游戏&#xff1a; 密码学。 查看API文档&#xff1a; package cn.ensource.random;import java.util.Random;public class RandomDemo1 {public static voi…

强化学习的优化策略PPO和DPO

DPO DPO(直接偏好优化)简化了RLHF流程。它的工作原理是创建人类偏好对的数据集&#xff0c;每个偏好对都包含一个提示和两种可能的完成方式——一种是首选&#xff0c;一种是不受欢迎。然后对LLM进行微调&#xff0c;以最大限度地提高生成首选完成的可能性&#xff0c;并最大限…

win编写bat脚本启动java服务

新建txt&#xff0c;编写&#xff0c;前台启动&#xff0c;出现cmd黑窗口 echo off start java -jar zhoao1.jar start java -jar zhoao2.jar pause完成后&#xff0c;重命名.bat 1、后台启动&#xff0c;不出现cmd黑窗口&#xff0c;app是窗口名称 echo off start "名…

如何同步管理1000个设备的VLAN数据?

什么是VLAN&#xff1f; VLAN&#xff0c;也就是虚拟局域网&#xff0c;是通过为子网提供数据链路连接来抽象出局域网的概念。在企业网中&#xff0c;一个企业级交换机一般是24口或者是48口&#xff0c;连接这些接口的终端在物理上形成一个广播域。广播域过大&#xff0c;就会导…

异地组网群晖不能访问怎么办?

在日常使用群晖网络储存设备时&#xff0c;我们常常会遇到无法访问的情况&#xff0c;特别是在异地组网时。这个问题很常见&#xff0c;但也很让人困扰。本文将针对异地组网群晖无法访问的问题进行详细解答和分析。 异地组网的问题 在异地组网中&#xff0c;群晖设备无法访问的…

Unity设计模式之工厂模式

什么是工厂模式&#xff1f; 工厂是一种创建型设计模式。通俗来讲就是提供一种封装对象创建的方式&#xff0c;将对象的创建和使用区分开。就是Unity里面通常用到的创建和管理对象。 工厂模式有什么优点&#xff1f; 1、封装对象的创建方式&#xff0c;使其更加灵活、易于管理…