C++ 连接redis数据库并处理数据(保姆级)

注:此文档为保姆级C++ 连接redis数据库并处理数据模板,将从redis的下载安装使用样例开始一步一步讲起

一、Redis下载安装及基础命令讲解(可跳过

注:此部分为给对redis0基础的提供一定的帮助,已有相应基础的可跳过,为省略篇幅直入主题,在下方不提供具体内容,提供相应的链接,有需要自行查看。

1.Windows平台

Window下Redis的安装和部署详细图文教程

Window下Redis的安装和部署详细图文教程(Redis的安装和可视化工具的使用)_redis安装-优快云博客

2.linux平台

Linux系统Redis安装及配置

https://blog.youkuaiyun.com/Mr_hwt_123/article/details/135553479

二、C++ 使用Hiredis库调用redis

1.Hiredis库下载安装

windows下编译和使用hiredis

windows下编译和使用hiredis_hiredis windows-优快云博客

Linux下hiredis的安装以及使用

Linux下hiredis的安装以及使用_collecting hiredis==0.3.1 (from sentry) using cach-优快云博客

注:此部分建议非必要尽量使用linux系统,windows配置hiredis问题较多,流程复杂。

2.C++ 使用Hiredis库调用redis样例

#include <string>
#include <vector>
#include <thread>
#include <filesystem>


//Eigen
#include <Eigen/Core>
#include <Eigen/Eigenvalues>
#include <Eigen/Dense>

// json
#include "include/nlohmann/json.hpp"

// hiredis
#include <hiredis/hiredis.h>



// opencv
#include <opencv2/core/eigen.hpp>
#include <opencv2/opencv.hpp>




//Boost
#include <boost/filesystem.hpp>
#include <boost/thread/thread.hpp>



namespace fs = std::filesystem;
using json = nlohmann::json;

// 定义一个结构体来封装相关的参数
struct OperationStatus
{
    std::string algorithmMessage;
    std::string errorMessage = "";
    std::string originalData;
    std::string jsonName = "";
    std::string modelName = "";
    std::string cameraNumber = "";
};

OperationStatus globalStatus;

//此部分为函数声明,应放至对应的头文件中,本文档为便于观看则将声明与定义放到一起

redisContext *safe_connect_redis(const char *host, int port, int max_retries, int retry_delay_seconds, int connect_timeout_ms = 5000);

bool is_queue_nonempty(redisContext *context, const std::string &queue_name);

std::string pop_from_queue(redisContext *redis_context, const std::string &queue_key);

void parse_popped_data(const std::string &data, std::string &original_data,
                       std::string &cameraSeriesNumber, std::string &sideImageFolder, std::string &saveFolder, int &sign);
					   
bool push_to_queue(redisContext *redis_context, const std::string &queue_key, const std::string &value);

void process_redis_queue(redisContext *context, const std::string &queue_name, int wait_interval_seconds = 2);
					   


// 安全连接Redis,带重试
redisContext *safe_connect_redis(const char *host, int port, int max_retries, int retry_delay_seconds, int connect_timeout_ms = 5000)
{
    redisContext *context = nullptr;
    struct timeval timeout = {connect_timeout_ms / 1000, (connect_timeout_ms % 1000) * 1000}; // Convert to timeval format
    int retries = 0;

    while (retries <= max_retries)
    {
        context = redisConnectWithTimeout(host, port, timeout);
        if (context == nullptr || context->err)
        {
            std::cerr << "Redis connection error: " << (context ? context->errstr : "No context") << ". Retrying..." << std::endl;
            if (context)
                redisFree(context);
            context = nullptr;
            std::this_thread::sleep_for(std::chrono::seconds(retry_delay_seconds));
            retries++;
        }
        else
        {
            break;
        }
    }
    return context;
}


//检查队列是否非空
bool is_queue_nonempty(redisContext *context, const std::string &queue_name)
{
    if (context == nullptr || queue_name.empty())
    {
        return false; // 如果上下文或队列名无效,直接返回false
    }

    // 使用 LLEN 命令检查队列长度
    std::string len_cmd = "LLEN " + std::string(queue_name);
    redisReply *reply = (redisReply *)redisCommand(context, len_cmd.c_str());
    int queue_length = reply->integer;
    if (reply != nullptr && reply->type == REDIS_REPLY_INTEGER)
    {
        // queue_length = reply->integer;
        if (queue_length == 0)
        {
            // 获取当前时间
            auto now = std::chrono::system_clock::now();
            std::time_t now_c = std::chrono::system_clock::to_time_t(now);
            // 使用std::string存储格式化后的时间字符串
            std::string time_str(80, '\0');                                                  // 初始化一个足够大的字符串,避免重新分配
            strftime(&time_str[0], time_str.size(), "%Y-%m-%d %H:%M:%S", localtime(&now_c)); // 注意时间格式化到已初始化的字符串

            // 去除可能的空余字符(虽然在这个例子中不会有多余,但作为好习惯)
            time_str.resize(strlen(time_str.c_str()));

            // 输出当前时间和提示信息
            std::cout << "当前时间为: " << time_str << ",队列 " << queue_name << " 为空。" << std::endl;
        }
        else
        {
            std::cout << "队列 " << queue_name << " 非空,包含 " << queue_length << " 个元素。" << std::endl;
        }
    }
    else
    {
        std::cerr << "查询队列长度时发生错误: " << (reply ? reply->str : "No reply") << std::endl;

        if (reply)
            freeReplyObject(reply);

        return false;
    }

    if (reply)
        freeReplyObject(reply);

    return queue_length;
}

// 从队列中弹出(取出)一个元素
std::string pop_from_queue(redisContext *redis_context, const std::string &queue_key)
{
    if (redis_context == nullptr)
    {
        std::cerr << "Redis context is not valid." << std::endl;
        return "";
    }

    // 构建并发送POP命令,这里使用LPOP作为示例,根据需求也可以使用RPOP
    std::string pop_cmd = "LPOP " + queue_key;
    redisReply *reply = (redisReply *)redisCommand(redis_context, pop_cmd.c_str());

    if (reply == nullptr)
    {
        std::cerr << "Failed to execute POP command." << std::endl;
        return "";
    }
    else if (reply->type == REDIS_REPLY_STRING)
    {
        std::string value(reply->str, reply->len);
        freeReplyObject(reply); // 释放reply对象
        return value;
    }
    else if (reply->type == REDIS_REPLY_NIL)
    {
        std::cout << "Queue is empty." << std::endl;
        freeReplyObject(reply);
        return "";
    }
    else
    {
        std::cerr << "Unexpected reply type: " << reply->type << std::endl;
        freeReplyObject(reply);
        return "";
    }
}

// 用于解析从队列弹出的数据,即解析对接的软件端提供的redis队列信息
void parse_popped_data(const std::string &data, std::string &original_data,
                       std::string &cameraSeriesNumber, std::string &sideImageFolder, std::string &saveFolder, int &sign)
{
    try
    {
        json jsonData = json::parse(data); // 解析JSON字符串

        // 提取各个字段
        original_data = jsonData.value("original_data", "");
        cameraSeriesNumber = jsonData.value("cameraSeriesNumber", "");
        sideImageFolder = jsonData.value("sideImageFolder", "");
        saveFolder = jsonData.value("saveFolder", "");
        sign = jsonData.value("sign", 0);

        // 打印或进一步处理这些值
        std::cout << "Original Data: " << original_data << std::endl;
        globalStatus.originalData = original_data;
        std::cout << "Camera Series Number: " << cameraSeriesNumber << std::endl;
        std::cout << "Side Image Folder: " << sideImageFolder << std::endl;
        std::cout << "Save Folder: " << saveFolder << std::endl;
        std::cout << "Sign: " << sign << std::endl;
    }
    catch (json::exception &e)
    {
        std::cerr << "Error parsing JSON: " << e.what() << std::endl;
    }
}

// 用于构建并推送特定情况的JSON消息到Redis
void pushStatusToRedis(redisContext *redis_context, const OperationStatus &status)
{
    json statusJson;

    if (!status.errorMessage.empty())
    {
        statusJson["code"] = -1;
        statusJson["msg"] = status.errorMessage;
    }

    statusJson["code"] = 0;
    statusJson["msg"] = status.algorithmMessage;

    // 构建嵌套的"data"字段
    json dataJson;
    dataJson["original_data"] = status.originalData;
    dataJson["json_name"] = status.jsonName;
    dataJson["model_name"] = status.modelName;
    dataJson["camera_number"] = status.cameraNumber;

    statusJson["data"] = dataJson; // 将dataJson添加到主JSON对象中

    const std::string jsonString = statusJson.dump();           // 序列化JSON对象为字符串
    const std::string queueKey = "xxx"; // Redis队列的key

    if (!push_to_queue(redis_context, queueKey, jsonString))
    {
        std::cerr << "Failed to push status message to Redis after operation " << std::endl;
    }
}

// 向队列中推入(添加)一个元素
bool push_to_queue(redisContext *redis_context, const std::string &queue_key, const std::string &value)
{
    if (redis_context == nullptr)
    {
        std::cerr << "Redis context is not valid." << std::endl;
        return false;
    }
    std::cout << "json value: " << value << std::endl;

    // 构建并发送PUSH命令,这里使用LPUSH作为示例,根据需求也可以使用RPUSH
    std::string push_cmd = "LPUSH " + queue_key + " " + value;
    redisReply *reply = (redisReply *)redisCommand(redis_context, push_cmd.c_str());

    if (reply == nullptr)
    {
        std::cerr << "Failed to execute PUSH command." << std::endl;
        return false;
    }
    else if (reply->type == REDIS_REPLY_INTEGER)
    {
        // 成功推入,返回队列的新长度
        std::cout << "Redis Value pushed Success. Queue length: " << reply->integer << " ,pushed to Queue context is " << value << std::endl;
        freeReplyObject(reply);
        return true;
    }
    else
    {
        std::cerr << "Unexpected reply type: " << reply->type << std::endl;
        freeReplyObject(reply);
        return false;
    }
}


void process_redis_queue(redisContext *context, const std::string &queue_name, int wait_interval_seconds = 2)
{
    while (true)
    {
        // 检查队列是否非空
        bool has_data = is_queue_nonempty(context, queue_name);
        if (!has_data)
        {
            std::cout << "队列 " << queue_name << " 为空,等待数据..." << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(wait_interval_seconds)); // 等待
            continue;
        }

        // 队列非空,处理数据
        std::cout << "队列 " << queue_name << " 有数据,开始处理..." << std::endl;

        std::string request_data = pop_from_queue(context, queue_name);
        std::cout << "Request from queue: " << request_data << std::endl;
        std::string original_data, cameraSeriesNumber, sideImageFolder, saveFolder;
        int sign;
        if (!request_data.empty())
        {
            parse_popped_data(request_data, original_data, cameraSeriesNumber, sideImageFolder,
                              saveFolder, sign);
        }
        const std::string input_dir = sideImageFolder;
        std::cout << "algorithms input dir: " << input_dir << std::endl;

        // 调用算法函数,其他部分代码皆为通用,此部分应替换为相应的算法函数
        algorithms(input_dir, saveFolder);

        // 输出对应的参数信息至redis中
        pushStatusToRedis(context, globalStatus);

        // 处理完数据后再次检查队列状态,决定是否继续循环
        // 这里假设处理完数据后可能队列变空或仍有数据,因此需要重新检查
        has_data = is_queue_nonempty(context, queue_name);
        if (!has_data)
        {
            std::cout << "数据处理完毕,队列变为空,等待更多数据..." << std::endl;
        }
    }
}

int main()
{
    std::cout << "Starting connect to Redis..." << std::endl;

    // 尝试连接Redis
    redisContext *redis_context = safe_connect_redis("127.0.0.1", 6479, 3, 5, 2000); // 最后一个参数是连接超时时间,单位毫秒;
    if (redis_context == nullptr)
    {
        std::cerr << "Failed to establish initial connection to Redis. Exiting program." << std::endl;
        return -1;
    }
    else
    {
        std::cout << "Connected to Redis successfully. Starting processing loop." << std::endl;
        // 示例:从请求队列中弹出一个元素,此处队列的键值为对接双方沟通的值,本次项目为xxx
        std::string request_queue_key = "xxx";
		//通过键值去访问相应的队列并处理对应的值
        process_redis_queue(redis_context, request_queue_key);
    }


    // 注意:在实际应用中,process_redis_queue()中存在,通常不会返回,除非有特定的退出逻辑
    // 或者在异常情况下,如进程被终止,因此这部分下面这部分代码可能不需要
    std::cout << "Processing loop exited unexpectedly. Cleaning up and exiting program." << std::endl;
    redisFree(redis_context);
    return 0;
}

注:windows下对于cpp代码,将使用VS IDE配置环境,然后编译运行即可,而在linux下,则需要编写CMakeLists.txt去实现此代码的编译运行,若对cmakelist不了解,可参考下面的CMakeLists.txt文件

cmake_minimum_required(VERSION 2.8)
project(xxx)

set(CMAKE_CXX_STANDARD 17)

#option(BUILD_SHARED_LIBS "Build turntable_camera_calibration as library" ON)

# ----------- find dependencies -----------
# OpenCV
find_package(OpenCV 4 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
message(STATUS "OpenCV version: ${OpenCV_VERSION}")
find_package(Eigen3 REQUIRED)
include_directories(${Eigen_INLUDE_DIRS})
message(STATUS "Eigen3 version: " ${Eigen3_VERSION})
# Boost
find_package(Boost 1.74.0 REQUIRED COMPONENTS filesystem)
include_directories(${Boost_INCLUDE_DIRS})
message(STATUS "Boost version: " ${Boost_VERSION})
# PCL
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
message(STATUS "PCL version: " ${PCL_VERSION})



# Add nlohmann/json - No need to find_package since it's a header-only library
# Include the directory where nlohmann/json.hpp is located
include_directories(${CMAKE_SOURCE_DIR}/include) # Replace with actual path



add_executable(${PROJECT_NAME} xxx.cpp)

target_link_libraries(${PROJECT_NAME} ${PCL_LIBRARIES} ${OpenCV_LIBS} ${Boost_LIBRARIES} hiredis)


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值