在开发过程中,我们经常会遇到需要在嵌入式Linux系统上与Redis进行交互的需求。因此选择一个适合的Redis客户端库就显得尤为重要。下面介绍下c++中有名的redis-plus-plus(redis++)三方库在嵌入式linux下的交叉编译及使用。该库底层是基于hiredis的良好封装,具备良好的性能。
在C++环境下,hiredis
和hirredisvip
是最基础的选择,但它们并没有封装连接池、自动重连等功能,这使得它们在复杂场景中显得力不从心。特别是hiredis
,它本身不是线程安全的,每个线程应该维护自己的连接对象,不可在多个线程之间共享一个连接对象。多个线程同时使用同一个连接对象会导致数据竞争和不确定的行为。
还有其他一些库如cloredis
,它能够支持集群和单机模式,并且自带连接池功能,但它对Redis哨兵模式的支持并不理想。而redisplus plus
这个库,不仅支持Redis的大多数功能,而且提供了更高级的封装和特性,比如自动重连、连接池、哨兵模式等,极大地方便了我们的开发工作。
Redis++介绍
redis++
是Redis官网推荐的C++连接库之一,其开源地址为:https://github.com/sewenew/redis-plus-plus。这个库基于hiredis
,但提供了更为强大的功能和更好的使用体验。它支持Redis的大多数特性,并对这些特性进行了C++风格的封装,使得在C++项目中使用Redis更加得心应手。
准备交叉编译工具链
在嵌入式Linux开发中,我们通常需要进行交叉编译。这里以ARM和RISC-V架构为例,提供一个配置文件toolchain.cmake
,用于设置交叉编译工具链。
# 交叉编译工具链配置文件
# 用于嵌入式Linux系统和RISC-V MCU的交叉编译
# 设置系统名称
set(CMAKE_SYSTEM_NAME Linux)
# 设置处理器架构变量,可以通过命令行参数传入
# 例如: cmake -DTARGET_ARCH=arm ..
if(NOT DEFINED TARGET_ARCH)
set(TARGET_ARCH "arm" CACHE STRING "Target architecture (arm or riscv)")
endif()
# 设置ARM工具链路径
set(ARM_TOOLCHAIN_PATH "/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi")
# 设置RISC-V工具链路径
set(RISCV_TOOLCHAIN_PATH "/opt/tronlong/tina5.0_v1.0/rtos/lichee/rtos/tools/riscv64-elf-x86_64-20201104")
# 根据目标架构设置主工具链
if(${TARGET_ARCH} STREQUAL "arm")
# ARM Linux工具链配置
set(CMAKE_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-g++)
set(CMAKE_FIND_ROOT_PATH /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi/)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 设置额外的编译标志
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)
# 设置链接器
set(CMAKE_LINKER ${CMAKE_C_COMPILER})
set(CMAKE_AR ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ar)
set(CMAKE_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ranlib)
elseif(${TARGET_ARCH} STREQUAL "riscv")
# RISC-V工具链配置 (C906核心)
set(CMAKE_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)
set(CMAKE_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)
set(CMAKE_FIND_ROOT_PATH ${RISCV_TOOLCHAIN_PATH}/riscv64-unknown-elf)
set(CMAKE_SYSTEM_PROCESSOR riscv)
# 设置RISC-V特定的编译标志 (C906核心)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)
# 设置链接器
set(CMAKE_LINKER ${CMAKE_C_COMPILER})
set(CMAKE_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)
set(CMAKE_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)
else()
message(FATAL_ERROR "不支持的目标架构: ${TARGET_ARCH}. 请使用 'arm' 或 'riscv'")
endif()
# 设置查找规则
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# 禁用系统库路径
set(CMAKE_SKIP_RPATH TRUE)
# 设置交叉编译环境的库和头文件搜索路径
set(CMAKE_SYSROOT ${CMAKE_FIND_ROOT_PATH})
# 关键!!!,不能漏掉这个设置,如果需要安装到特定路径,特别是三方库安装需要到这里找依赖
set(CMAKE_FIND_ROOT_PATH /root/arm_install)
这个配置文件设置了目标系统的名称、处理器架构、工具链路径等重要信息。根据需要,我们可以选择编译针对ARM或RISC-V架构的库文件。
交叉编译Hiredis库
首先,我们需要交叉编译hiredis
库,因为redis++
是基于hiredis
的。在项目根目录下创建一个build
目录,并在其中执行以下命令:
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake -DTARGET_ARCH=arm -DCMAKE_INSTALL_PREFIX=${HOME}/arm_install
make
make install
这样,hiredis
库就会被交叉编译并安装到${HOME}/arm_install
目录下。
交叉编译Redis++库
接下来,我们使用相同的工具链文件来交叉编译redis++
。在redis++
的源码目录下创建一个build
目录,并在其中执行以下命令:
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake -DTARGET_ARCH=arm -DCMAKE_INSTALL_PREFIX=${HOME}/arm_install -DCMAKE_PREFIX_PATH=${HOME}/arm_install -DREDIS_PLUS_PLUS_CXX_STANDARD=11 -DREDIS_PLUS_PLUS_BUILD_TEST=OFF
make
make install
这里,我们启用了C++11标准,并且关闭了测试功能,以加快编译速度。编译完成后,redis++
库也会被安装到${HOME}/arm_install
目录下。
使用Redis++库
下面是一个简单的示例代码,展示了如何使用redis++
库。这个示例演示了如何连接到Redis服务器,并进行一些基本的操作,比如读取Hash数据结构中的内容。
#include <unistd.h>
#include <chrono>
#include <tuple>
#include <iostream>
#include <vector>
#include <map>
#include <unordered_set>
#include <sw/redis++/redis++.h>
#include <sw/redis++/sentinel.h>
#include <sw/redis++/connection.h>
#include <sw/redis++/connection_pool.h>
using namespace sw::redis;
using namespace std::chrono;
int main() {
// 设置连接选项
ConnectionOptions connection_options;
connection_options.host = "192.168.11.85"; // 必需,Redis服务器地址
connection_options.port = 16379; // 可选,默认端口是6379
connection_options.db = 5; // 可选,默认使用第0个数据库
// 设置连接池选项
ConnectionPoolOptions pool_options;
pool_options.size = 3; // 连接池大小,即最大连接数
pool_options.wait_timeout = std::chrono::milliseconds(100);
// 创建Redis连接实例
Redis redis(connection_options, pool_options);
// 使用hscan命令遍历Hash中的元素
auto cursor = 0LL;
auto pattern = "*";
auto count = 5;
std::map<std::string, std::string> hashs;
while (true) {
cursor = redis.hscan("FORWARD.PLAT.DETAIL", cursor, pattern, count, std::inserter(hashs, hashs.begin()));
if (cursor == 0) {
break;
}
}
if(hashs.size() < 1) {
printf("我们没有获取到任何数据!\n");
} else {
for(auto it1 = hashs.begin(); it1 != hashs.end(); it1++) {
std::cout << "Plat ID:" << it1->first << std::endl;
std::cout << "Plat UserName & Password:" << it1->second << std::endl;
}
}
// 使用hget命令获取Hash中指定key的value
OptionalString strValue = redis.hget("XNY.CARINFO", "CRC01211711100232");
if (strValue) {
std::cout << "CRC01211711100232 的 vin:" << *strValue << std::endl;
std::string straa = *strValue;
if(straa.empty()) {
std::cout << "我们没有获取到任何数据!" << std::endl;
} else {
std::cout << "---- CRC01211711100232 的 details:" << straa << std::endl;
}
} else {
std::cout << "CRC01211711100232 的 vin 不存在!" << std::endl;
}
// 使用hincrby命令对Hash中的某个数值字段进行自增操作
std::cout << "---- 下面我们来试试hincrby ---- " << std::endl;
auto cursor2 = 0LL;
auto pattern2 = "*";
auto count2 = 20;
std::map<std::string, std::string> vv;
std::vector<std::string> vlist;
while (true) {
cursor2 = redis.hscan("FORWARD.LIST.002", cursor2, pattern2, count2, std::inserter(vv, vv.begin()));
if (cursor2 == 0) {
break;
}
}
for(auto it1 = vv.begin(); it1 != vv.end(); it1++) {
vlist.push_back(it1->first);
}
for(auto uu = vlist.begin(); uu != vlist.end(); uu++) {
std::cout << *uu << std::endl;
}
return 0;
}
在这段代码中,我们首先设置了连接选项和连接池选项,然后创建了一个Redis
对象。接着,我们使用hscan
命令遍历了一个Hash数据结构中的所有元素,并使用hget
命令获取了一个特定key的value。最后,我们尝试使用hincrby
命令对一个数值字段进行了自增操作。
结论
通过使用redis++
库,我们可以更方便地在嵌入式Linux系统中与Redis进行交互。它提供了丰富的功能和易于使用的接口,极大地方便了开发工作。希望这篇博文能够帮助到需要在嵌入式Linux系统中使用Redis的开发者。