封装Redis使用的初步探索

开发环境

基于C++提出一套接口,并使用redis源码实现,对外提供一个CRedisHandler的方法类,将redis的相关源码和依赖编译入IxRedisHandler-Static静态库内。
即对外提供的内容:

  • IxRedisHandler.h
  • IxRedisHandler-Static.lib

源码下载地址:https://github.com/redis/hiredis
服务器下载地址:https://redis.io/download
本文使用的服务器版本号为:5.0.10

在验证redis功能之前,记住要启动redis-server服务器。
在Windows上的操作示例如下:
在这里插入图片描述

工程建立

使用CMakeLists.txt组织工程,编译出以上支持的在VS平台上使用的库。

CMakeLists.txt组织工程:

在这里插入图片描述
根CMakeLists.txt的关键部分,即关联RedisClientApp和IxRedisHandler-Static,以及hiredis源码

#--------------------------------------------------------------------
# 添加子目录
#--------------------------------------------------------------------
add_subdirectory(hiredis)
add_subdirectory(IxRedisHandler-Static)
add_subdirectory(RedisClientApp)

IxRedisHandler-Static的CMakeLists.txt

hiredis的源码直接提供了CMakeLists.txt文件,所以可以直接加入,里面会有三个工程建立:hiredis-test、hiredis_static、hiredis,如果不需要可以手动改hiredis提供的CMakeLists.txt。
不过对于想要达到的目的来说,hiredis提供的CMakeLists.txt并不重要,因为我们只需要编出将redis相关方法编入了的 IxRedisHandler-Static即可。

IxRedisHandler-Static的CMakeLists.txt实现是直接将redis需要的源码全部编入的方法,然后对外只提供CRedisHandler类接口调用:

# 工程名称
set(PROJECT_NAME IxRedisHandler-Static)
project(${PROJECT_NAME}
    VERSION ${CMAKE_PROJECT_VERSION})

# Hiredis requires C99
INCLUDE(GNUInstallDirs)
SET(CMAKE_C_STANDARD 99)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
SET(CMAKE_DEBUG_POSTFIX d)

#------------------------------------------------
# 依赖库/框架
#------------------------------------------------

#--------------------------------------------------------------------
# 源代码文件
#--------------------------------------------------------------------

# 生成target
add_library(${PROJECT_NAME} STATIC)

file(GLOB INC_FILES Include/*.h)
file(GLOB INC_FILES1 Src/*.cpp)
set(${PROJECT_NAME}_Includes
   ${INC_FILES}
   ${INC_FILES1}
)

aux_source_directory(Src CPP_FILES)
set(${PROJECT_NAME}_SRCS 
    ${CPP_FILES}
)

SET(hiredis_sources
    ../../hiredis/alloc.c
    ../../hiredis/async.c
    ../../hiredis/dict.c
    ../../hiredis/hiredis.c
    ../../hiredis/net.c
    ../../hiredis/read.c
    ../../hiredis/sds.c
    ../../hiredis/sockcompat.c
    ../../hiredis/hiredis.h
    ../../hiredis/read.h
    ../../hiredis/sds.h
    ../../hiredis/async.h
    ../../hiredis/alloc.h
    )

target_sources(${PROJECT_NAME}
    PRIVATE 
		${${PROJECT_NAME}_Includes}
        ${${PROJECT_NAME}_SRCS}
        ${hiredis_sources}
)

#--------------------------------------------------------------------
# 工程依赖
#--------------------------------------------------------------------

# 依赖库
#target_link_libraries(${PROJECT_NAME} 
#    PRIVATE
#        hiredis
#)

IF(WIN32 OR MINGW)
    TARGET_LINK_LIBRARIES(IxRedisHandler-Static PUBLIC ws2_32 crypt32)
ENDIF()

# 路径寻址
target_include_directories(${PROJECT_NAME}
    PRIVATE
        ./Include
)

#--------------------------------------------------------------------
# 事件
#--------------------------------------------------------------------

如果用CMake工具Build出一个VS2015的工程,界面大概呈现方式是这样的:
在这里插入图片描述

源码

redis命令

redis主要的调用方式主要在hiredis提供的源码test.c中也写得比较详细了,同时也可以直接启动hiredis-test编出的可执行文件进行验证。
如下是总结的Redis支持的数据类型相应的执行命令,也可以先用redis-cli.exe启动后,用命令行执行看结果:
因为最终封装也是基于Redis的命令提供接口。在这里插入图片描述

Redis for C++源码调用

以下展示几个关于操作数据库最关键的几个调用示例,以及主要验证了redis支持Binary的效果:

连接
redisContext* pConnect = redisConnect("127.0.0.1", 6379);
if (NULL == pConnect)
{
	std::cout << "redis build handler failed" << std::endl;
	return 0;
}
if (pConnect->err)
{
	std::cout << "connect error: %s\n" << pConnect->errstr << std::endl;
	return 0;
}
写入
// 方法一
redisReply* reply = (redisReply*)redisCommand(pConnect, "HMSET %s %d %s", "baidu-building", 2, "xxx23");
reply = (redisReply*)redisCommand(pConnect, "HMSET %s %d %s", "baidu-building", 3, "xxx33");
freeReplyObject(reply);
// 方法二
std::vector<int> vTest;
vTest.push_back(1);
vTest.push_back(22);
vTest.push_back(333);

int nLen = sizeof(int) * vTest.size();
char* pBuffer = new char[nLen];
memcpy(pBuffer, &vTest[0], nLen);

int paramN = 4; // 命令参数个数
std::string cmd = "HMSET";
std::string key = "baidu-building";
std::string strField = "1056";
const char* parms[] = { cmd.data() , key.data(), strField.data(), pBuffer }; // 命令
size_t paramsLens[] = { cmd.size() , key.size(), strField.size(), nLen };  // 命令中每个参数的个数
redisReply* r = (redisReply*)redisCommandArgv(pConnect, paramN, parms, paramsLens);
freeReplyObject(r);

delete[] pBuffer;
读取
// 方法一
redisReply* reply = (redisReply*)redisCommand(pConnect, "keys *", nMajor);
for (int i = 0; i < reply->elements; i++)
{
        std::cout << reply->element[i]->str << std::endl;
}
freeReplyObject(reply);
// 方法二
int paramN = 3; // 命令参数个数
std::string cmd = "HMGET";
std::string key = "baidu-building";
std::string strField = "1056";
const char* parms[] = { cmd.data() , key.data(), strField.data() }; // 命令
size_t paramsLens[] = { cmd.size() , key.size(), strField.size() };  // 命令中每个参数的个数
redisReply* r = (redisReply*)redisCommandArgv(pConnect, paramN, parms, paramsLens);

if (r->elements > 0)
{
	std::vector<int> vTest;
	vTest.resize(3);

	memcpy(&vTest[0], r->element[0]->str, r->element[0]->len);

	for (int i = 0; i < vTest.size(); i++)
	{
		std::cout << vTest[i] << "\t";
	}
}
freeReplyObject(r);
Redis返回结果

redisReply是redis的命令执行后的返回结果:
在这里插入图片描述
解析内容需要首先识别type类型,然后从integer/dval/len+str获取解析出的值。
在这里插入图片描述

接口提供

Redis作为非关系型数据库,其基本的封装肯定少不了连接和断开,包括数据请求,参数获取,事务支持等的操作,因此,这一层的封装主要是为屏蔽redis依赖库,避免C风格的调用方式。对于上层理解Redis,是作为一个数据库操作器使用,而这个操作器的每一个调用都不应该有副作用(没有指针未释放的细节处理),返回的结果是常规意义上的理解方式。

/**
 * @file IxRedisHandler.h
 * @brief Redis访问的Client操作器
 * @note 使用方法: 注意Redis持久化的方式:snapshotting和aof
 * @author Being
 * @date 2021/8/23
 * @version V00.00.01
 * @CopyRight swiet.com
 */
#ifndef IxRedisHandler_h__
#define IxRedisHandler_h__

#include <string>
#include <vector>

struct redisContext;
struct redisReply;
class CRedisHandler
{
public:

	CRedisHandler();
	~CRedisHandler();

	/**
	* @class IRedisReplyCallBack
	* @brief (Redis请求指令后)返回数据的回调类
	* @note  
	*/
	class IRedisReplyCallBack
	{
	public:

		virtual ~IRedisReplyCallBack() {}

		/**
		 * @fn  OnReplyString        
		 * @brief 响应Redis返回的二进制/字串类型数据
		 * @param[in] CRedisHandler * pHandler: Redis操作器
		 * @param[in] char * pBuffer: 二进制数据 
		 * @param[in] int nLen: 数据长度
		 * @return bool true 表示已被处理,截断;false表示未被处理,继续
		 */
		virtual bool OnReplyString(CRedisHandler* pHandler, char* pBuffer, int nLen) { return false; };

		/**
		 * @fn  OnReplyDouble        
		 * @brief 响应Redis返回的浮点数类型数据  
		 * @param[in] CRedisHandler * pHandler: Redis操作器 
		 * @param[in] double dValue: 数据
		 * @return bool true 表示已被处理,截断;false表示未被处理,继续
		 */
		virtual bool OnReplyDouble(CRedisHandler* pHandler, double dValue) { return false; };

		/**
		 * @fn  OnReplyInteger        
		 * @brief 响应Redis返回的整性数据     
		 * @param[in] CRedisHandler * pHandler: Redis操作器 
		 * @param[in] int64_t nValue: 数据
		 * @return bool true 表示已被处理,截断;false表示未被处理,继续
		 */
		virtual bool OnReplyInteger(CRedisHandler* pHandler, int64_t nValue) { return false; };
	};

	/**
	 * @fn  Initialize        
	 * @brief 初始化操作器,连接Redis服务器  
	 * @param[in] const std::string & strIP: 服务器IP
	 * @param[in] int nPort: 服务器端口号
	 * @param[in] int nDBIndex: 数据库编号(0-16)
	 * @return bool 是否初始化连接成功
	 */
	bool Initialize(const std::string& strIP, int nPort = 6379, int nDBIndex = 0);

	/**
	 * @fn  Uninitialze        
	 * @brief 卸载操作器,断开与Redis的服务  
	 * @return void
	 */
	void Uninitialze();

	/**
	 * @fn  IsValid        
	 * @brief 操作器当前是否处于可用状态  
	 * @return bool 是否可用
	 */
	bool IsValid() const;

	/**
	 * @fn  Reconnect        
	 * @brief 尝试重连
	 * @return void
	 */
	void Reconnect();

	/**
	 * @fn  GetIP        
	 * @brief 获取服务器IP地址
	 * @return std::string IP地址
	 */
	std::string GetIP() const;

	/**
	 * @fn  GetPort        
	 * @brief 获取服务器端口号  
	 * @return int 端口号
	 */
	int GetPort() const;

	/**
	 * @fn  GetDatabaseIndex        
	 * @brief 获取选择的数据库索引号  
	 * @return int 索引号
	 */
	int GetDatabaseIndex()const;

	/**
	 * @fn  QueryRedisVersion        
	 * @brief 请求Redis连接服务器的版本号  
	 * @param[in] int & rMajor: 主版本号
	 * @param[in] int & rMinor: 副版本号
	 * @return bool 是否请求成功
	 */
	bool QueryRedisVersion(int& rMajor, int& rMinor);

	/**
	 * @fn  CountKeys        
	 * @brief 计算当DB内的key个数  
	 * @return int 存中key个数
	 */
	int CountKeys();

	/**
	 * @fn  ExecuteSaveCommand        
	 * @brief 执行一次持久化命令(在snapshotting持久化操作下的一次bgsave)
	 * @return bool 是否执行成功
	 */
	bool ExecuteSaveCommand();

	/**
	 * @fn  ExcuteFlushDBKey        
	 * @brief 执行清空数据库所有Key操作  
	 * @return bool 是否执行成功
	 */
	bool ExcuteFlushDBKey();

	/**
	 * @fn  ExecuteRawCommand        
	 * @brief 执行Redis指令(直接输入)  
	 * @param[in] const std::string & strCommand: 指令
	 * @param[in] IRedisReplyCallBack * pReply: 响应数据回调
	 * @return bool 是否执行成功
	 */
	bool ExecuteRawCommand(const std::string& strCommand, IRedisReplyCallBack* pReply = NULL);

	/**
	 * @fn  ExecuteCommand        
	 * @brief 执行Redis指令(输入命令集)  
	 * @param[in] int nCommandCount: 指令个数 eg: 4
	 * @param[in] const char * * cArgvCommands: 指令数据数组 eg: {"HMSET", "building", "1056", pBuffer}
	 * @param[in] const size_t * cArgvCommandsLen: 指令长度数组 eg: {5, 8, 4, BufferLen}
	 * @param[in] IRedisReplyCallBack * pReply: 响应数据回调
	 * @return bool是否执行成功
	 */
	bool ExecuteCommand(int nCommandCount, const char** cArgvCommands, const size_t* cArgvCommandsLen, IRedisReplyCallBack* pReply = NULL);

public:	// 事务(Redis事务非原子性)

	/**
	 * @fn  Transaction        
	 * @brief 标记一个事务块开始  
	 * @return bool 是否启动成功
	 */
	bool Transaction();

	/**
	 * @fn  DiscardTransaction        
	 * @brief 取消事务,放弃执行事务块内的所有命令(与回滚不同,所以区别于Rollback)
	 * @return bool 是否取消成功
	 */
	bool DiscardTransaction();

	/**
	 * @fn  SetWatchKey        
	 * @brief 监视一个或多个key,如果在执行事务前,key被其它指令改动,事务被打断  
	 * @param[in] const std::vector<std::string> & vecKeys: key
	 * @return bool 是否设置成功
	 */
	bool SetWatchKey(const std::vector<std::string>& vecKeys);

	/**
	 * @fn  UnWatch        
	 * @brief 取消对所有key的监视
	 * @return bool 是否取消成功
	 */
	bool UnWatch();

	/**
	 * @fn  IsInTransaction        
	 * @brief 当前是否处于事务块中   
	 * @return bool 是否处于
	 */
	bool IsInTransaction() const;

	/**
	 * @fn  Commit        
	 * @brief 执行事务块内的所有命令  
	 * @return bool 是否执行成功
	 */
	bool Commit();

private:

	// true 表示已被处理,截断;false表示未被处理,继续
	bool ReplyCallBack(redisReply* reply, IRedisReplyCallBack* pReply);

private:

	redisContext* m_pHandler;
	std::string m_strIP;
	int m_nPort;
	int m_nDBIndex;

	bool m_bInTrasaction;
};


#endif // IxRedisHandler_h__

调用示例

封装完成后,可以在RedisClientApp中的main函数内进行测试:

class CReplayCallBack : public CRedisHandler::IRedisReplyCallBack
{
public:

	CReplayCallBack() {}
	~CReplayCallBack() {}

	virtual void OnReplyString(CRedisHandler* pHandler, char* pBuffer, int nLen)
	{
		std::cout << pBuffer << std::endl;
	}
};

int main(int argc, char* argv[])
{
	CRedisHandler redisHandler;
	bool bInit = redisHandler.Initialize("127.0.0.1");
	
	std::string str = "HMSET baidu-building 6 xxx33";
	redisHandler.ExecuteRawCommand(str);

	CReplayCallBack redisCallback;
	redisHandler.ExecuteRawCommand("keys *", &redisCallback);

	redisHandler.Uninitialze();
	getchar();

	return 0;
}

更复杂的调用方式在后面实际的应用场景中介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值