注:此文档为保姆级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)