标签搜索

目 录CONTENT

文章目录

Linux下使用C++操作redis数据库.md

小小城
2021-08-22 / 0 评论 / 0 点赞 / 5 阅读 / 11,195 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Linux下使用C++操作redis数据库

@[toc]

一、安装配置hiredis.h

C++来操作redis数据库。通过hiredis.h接口来实现,目前只能在Linux环境使用。

cd hiredis
  • 执行make && make install(自动把libhiredis.so放到/usr/local/lib/中,把hiredis.h放到/usr/local/inlcude/hiredis/中)
make && make install
  •  接下来在程序中就可以直接用了,在程序中包含#include <hiredis/hiredis.h>即可
  • 编译代码的时候需要加链接的库及库的路径,假设文件为redis.cpp,那么编译命令如下
g++ redis.cpp -o redis -L/usr/local/lib/ -lhiredis 
  • 在执行的时候如果出现动态库无法加载,那么需要进行如下配置
  • 在 /etc/ld.so.conf.d/ 目录下新建文件 usr-libs.conf ,内容是: /usr/local/lib
sudo vim /etc/ld.so.conf.d/usr-libs.conf
  • 然后使用命令 /sbin/ldconfig 更新一下配置即可。
sudo /sbin/ldconfig

二、接口介绍

1.建立链接:redisConnect

redisContext* redisConnect(const char *ip, int port)
  •  函数解释
  • 该函数用来连接redis数据库, 两个参数分别是redis数据库的ip和端口,端口号一般为6379
  • 该函数redisConnect用于创建所谓的redisContext。上下文是Hiredis保持连接状态的地方。
  • 当连接处于错误状态时,该redisContext 结构具有一个err非零的整数字段该字段errstr将包含带有错误描述的字符串
  • 使用尝试连接到Redis后redisConnect,应检查该err字段以查看建立连接是否成功
  • 类似的还提供了一个函数,供连接超时限定,即
redisContext* redisConnectWithTimeout(const char *ip, 
											int port, timeval tv)。
  • 注意注意,官方说明redisConnect函数不是线程安全的
  •  使用案例
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");
    }
}
  •  错误解释
  • 函数调用不成功时,取决于函数NULL还是REDIS_ERR返回。err上下文中的字段将为非零值,并设置为以下常量之一:
  • REDIS_ERR_IO:创建连接,尝试写入套接字或从套接字读取时发生I /O错误。如果您包含errno.h在应用程序中,则可以使用全局errno变量来找出问题所在。
  • REDIS_ERR_EOF:服务器关闭了连接,导致读取为空。
  • REDIS_ERR_PROTOCOL:解析协议时出错。
  • REDIS_ERR_OTHER:其他任何错误。当前,仅在无法解析要连接的指定主机名时使用。
  • 在每种情况下,errstr上下文中的字段都将设置为包含错误的字符串表示形式。

2.执行redis命令:redisCommand

void *redisCommand(redisContext *c, const char *format...)
  •  函数解释
  • 该函数用于执行redis数据库中的命令,第一个参数为连接数据库返回的redisContext,剩下的参数为变参,如同C语言中的prinf()函数。
  • 此函数的返回值为void*,但是一般会强制转换为redisReply类型,以便做进一步的处理。
  •  使用案例
首先介绍的是 redisCommand。此函数采用类似于printf的格式。最简单的形式是这样使用:
reply = redisCommand(context, "SET foo bar");

说明%s符在命令中插入一个字符串,并用于strlen确定字符串的长度:
reply = redisCommand(context, "SET foo %s", value);

当您需要在命令中传递二进制安全字符串时,%b可以使用说明符。与指向字符串
的指针一起,它需要字符串的size_tlength参数:
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

在内部,Hiredis将命令拆分为不同的参数,并将其转换为用于与Redis通信
的协议。一个或多个空格分隔参数,因此您可以在参数的任何位置使用说明符:
reply = redisCommand(context, "SET key:%s %s", myid, value);
void * redisCommandArgv(redisContext * c,int argc,
						const  char ** argv,const  size_t * argvlen);
  •  函数解释
  • 它需要参数的数量,argc字符串数组argv和参数的长度argvlen
  • 为了方便起见,argvlen可以将设置为NULL,并且函数将strlen(3)在每个参数上使用以确定其长度。
  • 显然,当任何一个参数需要二进制安全时,argvlen都应提供整个长度数组。
  • 返回值的语义与相同redisCommand。

3.释放redisCommand

void freeReplyObject(void *reply)
  •  函数解释
  • 释放redisCommand执行后返回的的redisReply所占用的内存。
  • redisCommand成功执行命令后,的返回值将保留答复。
  • 发生错误时,返回值为NULL并且err将设置上下文中的字段。返回错误后,上下文context将无法重用,您应该建立一个新的连接。
  • 该标准回答说redisCommand是的类型redisReply。中的 type字段redisReply应用于测试收到的回复类型:
  •  REDIS_REPLY_STATUS:

该命令回复了状态回复。可以使用访问状态字符串reply->str。可以使用访问此字符串的长度reply->len。

  •  REDIS_REPLY_ERROR:
  • 该命令回答了一个错误。错误字符串的访问方式与相同
  •  REDIS_REPLY_INTEGER:
  • 该命令以整数回答。可以使用reply->integer类型的字段访问整数值 long long。
  •  REDIS_REPLY_NIL:
  • 该命令回复了一个nil对象。没有数据可访问。
  •  REDIS_REPLY_STRING:
  • 大量(字符串)回复。可以使用来访问回复的值reply->str。可以使用访问此字符串的长度reply->len。
  •  REDIS_REPLY_ARRAY:
  • 多批量回复。多批量答复中的元素数存储在中 reply->elements。
  • 多批量回复中的每个元素也是一个redisReply对象,可以通过访问reply->element[..index..]。

应该使用freeReplyObject()函数释放答复。请注意,此函数将负责释放数组和嵌套数组中包含的子答复对象,因此用户无需释放子答复(这实际上是有害的,并且会破坏内存)。

4.断开连接:redisFree

 void redisFree(redisContext *c)
  • 此函数立即关闭套接字,然后释放在创建上下文时完成的分配。

5.流水线:Pipelining

为了解释Hiredis如何支持阻塞连接中的流水线操作,需要了解内部执行流程。

  •  redisCommand调用该系列中的任何功能时,Hiredis首先根据Redis协议格式化命令。
  •  然后将格式化的命令放入上下文的输出缓冲区中。此输出缓冲区是动态的,因此它可以容纳任意数量的命令。
  •  将命令放入输出缓冲区后,将redisGetReply被调用。该函数具有以下两个执行路径:
  •  输入缓冲区为非空:
  • 尝试解析来自输入缓冲区的单个答复并返回
  • 如果无法解析任何答复,请继续执行2
  •  输入缓冲区为空:
  • 将整个输出缓冲区写入套接字
  • 从套接字读取,直到可以解析单个答复
  •  该函数redisGetReply作为Hiredis API的一部分导出,可以在套接字上收到期望的答复时使用。
  •  对于管道命令,唯一需要做的就是填充输出缓冲区。为此redisCommand,除了不返回答复外,可以使用与该系列相同的两个命令:
void  redisAppendCommand(redisContext * c,const  char * format,...);
void  redisAppendCommandArgv(redisContext * c,int argc,
						const  char ** argv,const  size_t * argvlen);
  •  一次或多次调用函数后,redisGetReply可用于接收后续答复
  •  此函数的返回值是REDIS_OK或REDIS_ERR,其中后者表示读取答复时发生错误。就像其他命令一样,err上下文中的字段可用于找出导致此错误的原因
  •  以下示例显示了一个简单的管道(仅导致对的单个调用write(2)和对的单个调用read(2)):
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);
  •  此API也可以用于实现阻塞:
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);

while(redisGetReply(context,(void *)&reply) == REDIS_OK) 
{
    // consume message
    freeReplyObject(reply);
}

更多用法请点击https://github.com/redis/hiredis

三、简单封装hiredis

  •  redis_handler.h
#ifndef __REDIS_HANDLER_H__
#define __REDIS_HANDLER_H__
 
#include <hiredis/hiredis.h>
#include <string>

using namespace std;

enum
{
    M_REDIS_OK = 0, //执行成功
    M_CONNECT_FAIL = -1, //连接redis失败
    M_CONTEXT_ERROR = -2, //RedisContext返回错误
    M_REPLY_ERROR = -3, //redisReply错误
    M_EXE_COMMAND_ERROR = -4 //redis命令执行错误
};


class RedisHandler
{
public:
    RedisHandler();
    ~RedisHandler();
    int connect(const string &addr, int port, const string &pwd = ""); //连接redis数据库:addr:IP地址,port:端口号,pwd:密码(默认为空)
    int disConnect(); //断开连接

    int setValue(const string &key, const string &value); //添加或修改键值对,成功返回0,失败<0
    int getValue(const string &key, string &value); //获取键对应的值,成功返回0,失败<0
    int delKey(const string &key); //删除键,成功返回影响的行数,失败<0
    int printAll(); //打印所有的键值对

    string getErrorMsg(); //获取错误信息
private:
    string m_addr; //IP地址
    int m_port; //端口号
    string m_pwd; //密码
    redisContext* pm_rct; //redis结构体
    redisReply* pm_rr; //返回结构体
    string error_msg; //错误信息

    int connectAuth(const string &pwd); //使用密码登录
    int handleReply(void* value = NULL, redisReply ***array = NULL); //处理返回的结果
};


#endif
  •  redis_handler.cpp
#include "redis_handler.h"
#include <string>
#include <cstring>
#include <iostream>
using namespace std;

RedisHandler::RedisHandler()
{
    m_addr = "";
    m_port = 0;
    m_pwd = "";
    pm_rct = NULL;
    pm_rr = NULL;
    error_msg = "";
}

RedisHandler::~RedisHandler()
{
    disConnect();
    pm_rct = NULL;
    pm_rr = NULL;
}

/*
连接redis数据库
addr: 地址,port:端口号,pwd:密码
成功返回M_REDIS_OK,失败返回M_CONNECT_FAIL
*/
int RedisHandler::connect(const string &addr = "127.0.0.1", int port = 6379, const string &pwd) {
    m_addr = addr;
    m_port = port;
    m_pwd = pwd;

    pm_rct = redisConnect(m_addr.c_str(), m_port);

    if (pm_rct->err)
    {
        error_msg = pm_rct->errstr;
        return M_CONNECT_FAIL;
    }

    if (!m_pwd.empty())
    {
        return connectAuth(m_pwd);
    }

    return M_REDIS_OK;
}

/*
断开redis连接
*/
int RedisHandler::disConnect()
{
    redisFree(pm_rct);
    freeReplyObject(pm_rr);
}

/*
添加或插入键值对
key:键,value:值
成功返回M_REDIS_OK,失败返回<0
*/
int RedisHandler::setValue(const string &key, const string &value)
{
    string cmd = "set " + key + " " + value;

    pm_rr = (redisReply*)redisCommand(pm_rct, cmd.c_str());

    return handleReply();
}

/*
获取键对应的值
key:键,value:值引用
成功返回M_REDIS_OK,失败返回<0
*/
int RedisHandler::getValue(const string &key, string &value)
{
    string cmd = "get " + key;

    pm_rr = (redisReply*)redisCommand(pm_rct, cmd.c_str());

    int ret = handleReply(&value);
}

/*
删除键
key:键
成功返回影响的行数(可能为0),失败返回<0
*/
int RedisHandler::delKey(const string &key)
{
    string cmd = "del " + key;

    pm_rr = (redisReply*)redisCommand(pm_rct, cmd.c_str());

    int rows = 0;
    int ret = handleReply(&rows);
    if (ret == M_REDIS_OK)
        return rows;
    else
        return ret;
}

/*
打印所有键值对到屏幕上
*/
int RedisHandler::printAll()
{
    string cmd = "keys *";

    pm_rr = (redisReply*)redisCommand(pm_rct, cmd.c_str());

    int len ;
    redisReply **array;
    int ret = handleReply(&len, &array);
    if (ret == M_REDIS_OK)
    {
        for (int i = 0; i < len; i++)
            cout << string(array[i]->str) << endl;
    }
    else
        return 0;
}

/*
返回错误信息
*/
string RedisHandler::getErrorMsg()
{
    return error_msg;
}

/*
使用密码登录
psw:登录密码
成功返回M_REDIS_OK,失败返回<0
*/
int RedisHandler::connectAuth(const string &psw)
{
    string cmd = "auth " + psw;

    pm_rr = (redisReply*)redisCommand(pm_rct, cmd.c_str());

    return handleReply();
}

/*
处理redis返回的信息
value:数据指针,用于保存redis返回的基本类型(value指针指向该数据)
array:数组指针,用于保存redis返回的数组
成功返回M_REDIS_OK,失败返回<0
*/
int RedisHandler::handleReply(void* value, redisReply*** array)
{
    if (pm_rct->err)
    {
        error_msg = pm_rct->errstr;
        return M_CONTEXT_ERROR;
    }

    if (pm_rr == NULL)
    {
        error_msg = "auth redisReply is NULL";
        return M_REPLY_ERROR;
    }

    switch (pm_rr->type)
    {
    case REDIS_REPLY_ERROR:
        error_msg = pm_rr->str;
        return M_EXE_COMMAND_ERROR;
    case REDIS_REPLY_STATUS:
        if (!strcmp(pm_rr->str, "OK"))
            return M_REDIS_OK;
        else
        {
            error_msg = pm_rr->str;
            return M_EXE_COMMAND_ERROR;
        }
    case REDIS_REPLY_INTEGER:
        *(int*)value = pm_rr->integer;
        return M_REDIS_OK;
    case REDIS_REPLY_STRING:
        *(string*)value = pm_rr->str;
        return M_REDIS_OK;
    case REDIS_REPLY_NIL:
        *(string*)value = "";
        return M_REDIS_OK;
    case REDIS_REPLY_ARRAY:
        *(int*)value = pm_rr->elements;
        *array = pm_rr->element;
        return M_REDIS_OK;
    default:
        error_msg = "unknow reply type";
        return M_EXE_COMMAND_ERROR;
    }
}
  •  main.cpp
#include <iostream>
#include <string>
#include "redis_handler.h"
using namespace std;

int main()
{
    RedisHandler* rh = new RedisHandler();
    int ret;

    //连接测试
    cout << "错误测试: " << "地址错误" << endl;
    ret = rh->connect("34.15.14.15", 6379, "linesum");
    if (ret != M_REDIS_OK)
        cout << "redis error: " << rh->getErrorMsg() << endl;

    cout << "错误测试: " << "端口错误" << endl;
    ret = rh->connect("127.0.0.1", 1234, "linesum");
    if (ret != M_REDIS_OK)
        cout << "redis error: " << rh->getErrorMsg() << endl;

    cout << "错误测试: " << "密码错误" << endl;
    ret = rh->connect("127.0.0.1", 6479, "linsum");
    if (ret != M_REDIS_OK)
        cout << "redis error: " << rh->getErrorMsg() << endl;


    ret = rh->connect("127.0.0.1", 6479, "linesum");
    if (ret != M_REDIS_OK)
    {
        cout << "redis error: " << rh->getErrorMsg() << endl;
        return ret;
    }


    //set测试
    cout << "错误测试: " << "set不带value参数" << endl;
    ret = rh->setValue("key11", "");
    if (ret != M_REDIS_OK)
        cout << "redis error: " << rh->getErrorMsg() << endl;


    ret = rh->setValue("key11", "value11");
    if (ret != M_REDIS_OK)
    {
        cout << "redis error: " << rh->getErrorMsg() << endl;
        return ret;
    }

    ret = rh->setValue("key22", "value22");
    if (ret != M_REDIS_OK)
    {
        cout << "redis error: " << rh->getErrorMsg() << endl;
        return ret;
    }


    //get测试
    string str;
    cout << "错误测试: " << "get不带key参数" << endl;
    ret = rh->getValue("key1111", str);
    if (ret != M_REDIS_OK)
        cout << "redis error: " << rh->getErrorMsg() << endl;

    ret = rh->getValue("key11", str);
    if (ret != M_REDIS_OK)
    {
        cout << "redis error: " << rh->getErrorMsg() << endl;
        return ret;
    }
    else
        cout << "value : " << str << endl;



    //print测试
    ret = rh->printAll();
    if (ret != M_REDIS_OK)
    {
        cout << "redis error: " << rh->getErrorMsg() << endl;
        return ret;
    }


    //del测试
    cout << "错误测试: " << "删除不存在的key" << endl;
    ret = rh->delKey("key1111");
    if (ret != M_REDIS_OK)
        cout << "redis error: " << rh->getErrorMsg() << endl;


    ret = rh->delKey("key11");
    if (ret != M_REDIS_OK)
    {
        cout << "redis error: " << rh->getErrorMsg() << endl;
        return ret;
    }

    delete rh;

    return 0;
}
0

评论区