聊天项目(9) redis服务搭建

邮箱验证服务联调

我们启动GateServer和VarifyServer

我们启动客户端,点击注册按钮进入注册界面,输入邮箱并且点击获取验证码

https://cdn.llfc.club/1710646053282.jpg

GateServer收到Client发送的请求后,会调用grpc 服务 访问VarifyServer,VarifyServer会随机生成验证码,并且调用邮箱模块发送邮件给指定邮箱。而且把发送的结果给GateServer,GateServer再将消息回传给客户端。

设置验证码过期

我们的验证码是要设置过期的,可以用redis管理过期的验证码自动删除,key为邮箱,value为验证码,过期时间为3min。

windows 安装redis服务

windows 版本下载地址:

https://github.com/tporadowski/redis/releases

下载速度慢可以去我的网盘

链接: https://pan.baidu.com/s/1v_foHZLvBeJQMePSGnp4Ow?pwd=yid3 提取码: yid3

下载完成后解压

https://cdn.llfc.club/1710649614458.jpg

修改redis.windows.conf, 并且修改端口

  1. port 6380

找到requirepass foobared,下面添加requirepass

  1. # requirepass foobared
  2. requirepass 123456

启动redis 服务器.\redis-server.exe .\redis.windows.conf

https://cdn.llfc.club/1710649945760.jpg

启动客户端 .\redis-cli.exe -p 6380, 输入密码登录成功

https://cdn.llfc.club/1710650063208.jpg

Linux 安装redis服务

Linux安装容器后,直接用容器启动redis

  1. docker run -d --name llfc-redis -p 6380:6379 redis --requirepass "123456"

为了方便测试能否链接以及以后查看数据,大家可以下载redis desktop manager

官网链接
redisdesktop.com/

下载速度慢可以去我的网盘

链接: https://pan.baidu.com/s/1v_foHZLvBeJQMePSGnp4Ow?pwd=yid3 提取码: yid3

下载后安装

设置好ip和密码,点击测试连接连通就成功了

https://cdn.llfc.club/1710657223612.jpg

widows编译和配置redis

Linux的redis库直接编译安装即可,windows反而麻烦一些,我们先阐述windows环境如何配置redis库, C++ 的redis库有很多种,最常用的有hredis和redis-plus-plus. 我们用redis-plus-plus. 这里介绍一种简单的安装方式—-vcpkg

先安装vcpkg, 源码地址

https://github.com/microsoft/vcpkg/releases

下载源码后

windows版本redis下载地址

https://github.com/microsoftarchive/redis

因为是源码,所以进入msvc目录

https://cdn.llfc.club/1710725726234.jpg

用visual studio打开sln文件,弹出升级窗口, 我的是vs2019所以升级到142

https://cdn.llfc.club/1710725937787.jpg

只需要生成hiredis工程和Win32_Interop工程即可,分别点击生成,生成hiredis.lib和Win32_Interop.lib即可

右键两个工程的属性,代码生成里选择运行时库加载模式为MDD(Debug模式动态运行加载),为了兼容我们其他的库,其他的库也是MDD模式

https://cdn.llfc.club/1710726777016.jpg

编译Win32_Interop.lib时报错, system_error不是std成员,

https://cdn.llfc.club/1710727129177.jpg

解决办法为在Win32_variadicFunctor.cpp和Win32_FDAPI.cpp添加
#include <system_error>,再右键生成成功

https://cdn.llfc.club/1710729372811.jpg

将hiredis.lib和Win32_Interop.lib拷贝到D:\cppsoft\reids\lib

redis-3.0\depsredis-3.0\src文件夹拷贝到D:\cppsoft\reids

然后我们在visual studio中配置VC++ 包含目录

https://cdn.llfc.club/1710811823982.jpg

配置VC++库目录

https://cdn.llfc.club/1710811986563.jpg

然后在链接器->输入->附加依赖项中添加

https://cdn.llfc.club/1710812099185.jpg

代码测试

我们需要写代码测试库配置的情况

  1. void TestRedis() {
  2. //连接redis 需要启动才可以进行连接
  3. //redis默认监听端口为6387 可以再配置文件中修改
  4. redisContext* c = redisConnect("127.0.0.1", 6380);
  5. if (c->err)
  6. {
  7. printf("Connect to redisServer faile:%s\n", c->errstr);
  8. redisFree(c); return;
  9. }
  10. printf("Connect to redisServer Success\n");
  11. std::string redis_password = "123456";
  12. redisReply* r = (redisReply*)redisCommand(c, "AUTH %s", redis_password);
  13. if (r->type == REDIS_REPLY_ERROR) {
  14. printf("Redis认证失败!\n");
  15. }else {
  16. printf("Redis认证成功!\n");
  17. }
  18. //为redis设置key
  19. const char* command1 = "set stest1 value1";
  20. //执行redis命令行
  21. r = (redisReply*)redisCommand(c, command1);
  22. //如果返回NULL则说明执行失败
  23. if (NULL == r)
  24. {
  25. printf("Execut command1 failure\n");
  26. redisFree(c); return;
  27. }
  28. //如果执行失败则释放连接
  29. if (!(r->type == REDIS_REPLY_STATUS && (strcmp(r->str, "OK") == 0 || strcmp(r->str, "ok") == 0)))
  30. {
  31. printf("Failed to execute command[%s]\n", command1);
  32. freeReplyObject(r);
  33. redisFree(c); return;
  34. }
  35. //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
  36. freeReplyObject(r);
  37. printf("Succeed to execute command[%s]\n", command1);
  38. const char* command2 = "strlen stest1";
  39. r = (redisReply*)redisCommand(c, command2);
  40. //如果返回类型不是整形 则释放连接
  41. if (r->type != REDIS_REPLY_INTEGER)
  42. {
  43. printf("Failed to execute command[%s]\n", command2);
  44. freeReplyObject(r);
  45. redisFree(c); return;
  46. }
  47. //获取字符串长度
  48. int length = r->integer;
  49. freeReplyObject(r);
  50. printf("The length of 'stest1' is %d.\n", length);
  51. printf("Succeed to execute command[%s]\n", command2);
  52. //获取redis键值对信息
  53. const char* command3 = "get stest1";
  54. r = (redisReply*)redisCommand(c, command3);
  55. if (r->type != REDIS_REPLY_STRING)
  56. {
  57. printf("Failed to execute command[%s]\n", command3);
  58. freeReplyObject(r);
  59. redisFree(c); return;
  60. }
  61. printf("The value of 'stest1' is %s\n", r->str);
  62. freeReplyObject(r);
  63. printf("Succeed to execute command[%s]\n", command3);
  64. const char* command4 = "get stest2";
  65. r = (redisReply*)redisCommand(c, command4);
  66. if (r->type != REDIS_REPLY_NIL)
  67. {
  68. printf("Failed to execute command[%s]\n", command4);
  69. freeReplyObject(r);
  70. redisFree(c); return;
  71. }
  72. freeReplyObject(r);
  73. printf("Succeed to execute command[%s]\n", command4);
  74. //释放连接资源
  75. redisFree(c);
  76. }

在主函数中调用TestRedis,编译项目时发现编译失败,提示

https://cdn.llfc.club/1710812579501.jpg

在同时使用Redis连接和socket连接时,遇到了Win32_Interop.lib和WS2_32.lib冲突的问题, 因为我们底层用了socket作为网络通信,也用redis,导致两个库冲突。

引起原因主要是Redis库Win32_FDAPI.cpp有重新定义了socket的一些方法引起来冲突

  1. extern "C" {
  2. // Unix compatible FD based routines
  3. fdapi_accept accept = NULL;
  4. fdapi_access access = NULL;
  5. fdapi_bind bind = NULL;
  6. fdapi_connect connect = NULL;
  7. fdapi_fcntl fcntl = NULL;
  8. fdapi_fstat fdapi_fstat64 = NULL;
  9. fdapi_fsync fsync = NULL;
  10. fdapi_ftruncate ftruncate = NULL;
  11. fdapi_freeaddrinfo freeaddrinfo = NULL;
  12. fdapi_getaddrinfo getaddrinfo = NULL;
  13. fdapi_getpeername getpeername = NULL;
  14. fdapi_getsockname getsockname = NULL;
  15. fdapi_getsockopt getsockopt = NULL;
  16. fdapi_htonl htonl = NULL;
  17. fdapi_htons htons = NULL;
  18. fdapi_isatty isatty = NULL;
  19. fdapi_inet_ntop inet_ntop = NULL;
  20. fdapi_inet_pton inet_pton = NULL;
  21. fdapi_listen listen = NULL;
  22. fdapi_lseek64 lseek64 = NULL;
  23. fdapi_ntohl ntohl = NULL;
  24. fdapi_ntohs ntohs = NULL;
  25. fdapi_open open = NULL;
  26. fdapi_pipe pipe = NULL;
  27. fdapi_poll poll = NULL;
  28. fdapi_read read = NULL;
  29. fdapi_select select = NULL;
  30. fdapi_setsockopt setsockopt = NULL;
  31. fdapi_socket socket = NULL;
  32. fdapi_write write = NULL;
  33. }
  34. auto f_WSACleanup = dllfunctor_stdcall<int>("ws2_32.dll", "WSACleanup");
  35. auto f_WSAFDIsSet = dllfunctor_stdcall<int, SOCKET, fd_set*>("ws2_32.dll", "__WSAFDIsSet");
  36. auto f_WSAGetLastError = dllfunctor_stdcall<int>("ws2_32.dll", "WSAGetLastError");
  37. auto f_WSAGetOverlappedResult = dllfunctor_stdcall<BOOL, SOCKET, LPWSAOVERLAPPED, LPDWORD, BOOL, LPDWORD>("ws2_32.dll", "WSAGetOverlappedResult");
  38. auto f_WSADuplicateSocket = dllfunctor_stdcall<int, SOCKET, DWORD, LPWSAPROTOCOL_INFO>("ws2_32.dll", "WSADuplicateSocketW");
  39. auto f_WSAIoctl = dllfunctor_stdcall<int, SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD, LPVOID, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE>("ws2_32.dll", "WSAIoctl");
  40. auto f_WSARecv = dllfunctor_stdcall<int, SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE>("ws2_32.dll", "WSARecv");
  41. auto f_WSASocket = dllfunctor_stdcall<SOCKET, int, int, int, LPWSAPROTOCOL_INFO, GROUP, DWORD>("ws2_32.dll", "WSASocketW");
  42. auto f_WSASend = dllfunctor_stdcall<int, SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE>("ws2_32.dll", "WSASend");
  43. auto f_WSAStartup = dllfunctor_stdcall<int, WORD, LPWSADATA>("ws2_32.dll", "WSAStartup");
  44. auto f_ioctlsocket = dllfunctor_stdcall<int, SOCKET, long, u_long*>("ws2_32.dll", "ioctlsocket");
  45. auto f_accept = dllfunctor_stdcall<SOCKET, SOCKET, struct sockaddr*, int*>("ws2_32.dll", "accept");
  46. auto f_bind = dllfunctor_stdcall<int, SOCKET, const struct sockaddr*, int>("ws2_32.dll", "bind");
  47. auto f_closesocket = dllfunctor_stdcall<int, SOCKET>("ws2_32.dll", "closesocket");
  48. auto f_connect = dllfunctor_stdcall<int, SOCKET, const struct sockaddr*, int>("ws2_32.dll", "connect");
  49. auto f_freeaddrinfo = dllfunctor_stdcall<void, addrinfo*>("ws2_32.dll", "freeaddrinfo");
  50. auto f_getaddrinfo = dllfunctor_stdcall<int, PCSTR, PCSTR, const ADDRINFOA*, ADDRINFOA**>("ws2_32.dll", "getaddrinfo");
  51. auto f_gethostbyname = dllfunctor_stdcall<struct hostent*, const char*>("ws2_32.dll", "gethostbyname");
  52. auto f_getpeername = dllfunctor_stdcall<int, SOCKET, struct sockaddr*, int*>("ws2_32.dll", "getpeername");
  53. auto f_getsockname = dllfunctor_stdcall<int, SOCKET, struct sockaddr*, int*>("ws2_32.dll", "getsockname");
  54. auto f_getsockopt = dllfunctor_stdcall<int, SOCKET, int, int, char*, int*>("ws2_32.dll", "getsockopt");
  55. auto f_htonl = dllfunctor_stdcall<u_long, u_long>("ws2_32.dll", "htonl");
  56. auto f_htons = dllfunctor_stdcall<u_short, u_short>("ws2_32.dll", "htons");
  57. auto f_listen = dllfunctor_stdcall<int, SOCKET, int>("ws2_32.dll", "listen");
  58. auto f_ntohs = dllfunctor_stdcall<u_short, u_short>("ws2_32.dll", "ntohs");
  59. auto f_ntohl = dllfunctor_stdcall<u_long, u_long>("ws2_32.dll", "ntohl");
  60. auto f_recv = dllfunctor_stdcall<int, SOCKET, char*, int, int>("ws2_32.dll", "recv");
  61. auto f_select = dllfunctor_stdcall<int, int, fd_set*, fd_set*, fd_set*, const struct timeval*>("ws2_32.dll", "select");
  62. auto f_send = dllfunctor_stdcall<int, SOCKET, const char*, int, int>("ws2_32.dll", "send");
  63. auto f_setsockopt = dllfunctor_stdcall<int, SOCKET, int, int, const char*, int>("ws2_32.dll", "setsockopt");
  64. auto f_socket = dllfunctor_stdcall<SOCKET, int, int, int>("ws2_32.dll", "socket");

去掉Redis库里面的socket的函数的重定义,把所有使用这些方法的地方都改为下面对应的函数

  1. int FDAPI_accept(int rfd, struct sockaddr *addr, socklen_t *addrlen);
  2. int FDAPI_access(const char *pathname, int mode);
  3. int FDAPI_bind(int rfd, const struct sockaddr *addr, socklen_t addrlen);
  4. int FDAPI_connect(int rfd, const struct sockaddr *addr, size_t addrlen);
  5. int FDAPI_fcntl(int rfd, int cmd, int flags);
  6. int FDAPI_fstat64(int rfd, struct __stat64 *buffer);
  7. void FDAPI_freeaddrinfo(struct addrinfo *ai);
  8. int FDAPI_fsync(int rfd);
  9. int FDAPI_ftruncate(int rfd, PORT_LONGLONG length);
  10. int FDAPI_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
  11. int FDAPI_getsockopt(int rfd, int level, int optname, void *optval, socklen_t *optlen);
  12. int FDAPI_getpeername(int rfd, struct sockaddr *addr, socklen_t * addrlen);
  13. int FDAPI_getsockname(int rfd, struct sockaddr* addrsock, int* addrlen);
  14. u_long FDAPI_htonl(u_long hostlong);
  15. u_short FDAPI_htons(u_short hostshort);
  16. u_int FDAPI_ntohl(u_int netlong);
  17. u_short FDAPI_ntohs(u_short netshort);
  18. int FDAPI_open(const char * _Filename, int _OpenFlag, int flags);
  19. int FDAPI_pipe(int *pfds);
  20. int FDAPI_poll(struct pollfd *fds, nfds_t nfds, int timeout);
  21. int FDAPI_listen(int rfd, int backlog);
  22. int FDAPI_socket(int af, int type, int protocol);
  23. int FDAPI_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  24. int FDAPI_setsockopt(int rfd, int level, int optname, const void *optval, socklen_t optlen);
  25. ssize_t FDAPI_read(int rfd, void *buf, size_t count);
  26. ssize_t FDAPI_write(int rfd, const void *buf, size_t count);

考虑大家修改起来很麻烦,可以下载我的代码

https://gitee.com/secondtonone1/windows-redis

再次编译生成hredis和Win32_Interop的lib库,重新配置下,项目再次编译就通过了。

封装redis操作类

因为hredis提供的操作太别扭了,我们手动封装redis操作类,简化调用流程。

封装的类叫RedisMgr,它是个单例类并且可接受回调,按照我们之前的风格

  1. class RedisMgr: public Singleton<RedisMgr>,
  2. public std::enable_shared_from_this<RedisMgr>
  3. {
  4. friend class Singleton<RedisMgr>;
  5. public:
  6. ~RedisMgr();
  7. bool Connect(const std::string& host, int port);
  8. bool Get(const std::string &key, std::string& value);
  9. bool Set(const std::string &key, const std::string &value);
  10. bool Auth(const std::string &password);
  11. bool LPush(const std::string &key, const std::string &value);
  12. bool LPop(const std::string &key, std::string& value);
  13. bool RPush(const std::string& key, const std::string& value);
  14. bool RPop(const std::string& key, std::string& value);
  15. bool HSet(const std::string &key, const std::string &hkey, const std::string &value);
  16. bool HSet(const char* key, const char* hkey, const char* hvalue, size_t hvaluelen);
  17. std::string HGet(const std::string &key, const std::string &hkey);
  18. bool Del(const std::string &key);
  19. bool ExistsKey(const std::string &key);
  20. void Close();
  21. private:
  22. RedisMgr();
  23. redisContext* _connect;
  24. redisReply* _reply;
  25. };

连接操作

  1. bool RedisMgr::Connect(const std::string &host, int port)
  2. {
  3. this->_connect = redisConnect(host.c_str(), port);
  4. if (this->_connect != NULL && this->_connect->err)
  5. {
  6. std::cout << "connect error " << this->_connect->errstr << std::endl;
  7. return false;
  8. }
  9. return true;
  10. }

获取key对应的value

  1. bool RedisMgr::Get(const std::string &key, std::string& value)
  2. {
  3. this->_reply = (redisReply*)redisCommand(this->_connect, "GET %s", key.c_str());
  4. if (this->_reply == NULL) {
  5. std::cout << "[ GET " << key << " ] failed" << std::endl;
  6. freeReplyObject(this->_reply);
  7. return false;
  8. }
  9. if (this->_reply->type != REDIS_REPLY_STRING) {
  10. std::cout << "[ GET " << key << " ] failed" << std::endl;
  11. freeReplyObject(this->_reply);
  12. return false;
  13. }
  14. value = this->_reply->str;
  15. freeReplyObject(this->_reply);
  16. std::cout << "Succeed to execute command [ GET " << key << " ]" << std::endl;
  17. return true;
  18. }

设置key和value

  1. bool RedisMgr::Set(const std::string &key, const std::string &value){
  2. //执行redis命令行
  3. this->_reply = (redisReply*)redisCommand(this->_connect, "SET %s %s", key.c_str(), value.c_str());
  4. //如果返回NULL则说明执行失败
  5. if (NULL == this->_reply)
  6. {
  7. std::cout << "Execut command [ SET " << key << " "<< value << " ] failure ! " << std::endl;
  8. freeReplyObject(this->_reply);
  9. return false;
  10. }
  11. //如果执行失败则释放连接
  12. if (!(this->_reply->type == REDIS_REPLY_STATUS && (strcmp(this->_reply->str, "OK") == 0 || strcmp(this->_reply->str, "ok") == 0)))
  13. {
  14. std::cout << "Execut command [ SET " << key << " " << value << " ] failure ! " << std::endl;
  15. freeReplyObject(this->_reply);
  16. return false;
  17. }
  18. //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
  19. freeReplyObject(this->_reply);
  20. std::cout << "Execut command [ SET " << key << " " << value << " ] success ! " << std::endl;
  21. return true;
  22. }

密码认证

  1. bool RedisMgr::Auth(const std::string &password)
  2. {
  3. this->_reply = (redisReply*)redisCommand(this->_connect, "AUTH %s", password.c_str());
  4. if (this->_reply->type == REDIS_REPLY_ERROR) {
  5. std::cout << "认证失败" << std::endl;
  6. //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
  7. freeReplyObject(this->_reply);
  8. return false;
  9. }
  10. else {
  11. //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
  12. freeReplyObject(this->_reply);
  13. std::cout << "认证成功" << std::endl;
  14. return true;
  15. }
  16. }

左侧push

  1. bool RedisMgr::LPush(const std::string &key, const std::string &value)
  2. {
  3. this->_reply = (redisReply*)redisCommand(this->_connect, "LPUSH %s %s", key.c_str(), value.c_str());
  4. if (NULL == this->_reply)
  5. {
  6. std::cout << "Execut command [ LPUSH " << key << " " << value << " ] failure ! " << std::endl;
  7. freeReplyObject(this->_reply);
  8. return false;
  9. }
  10. if (this->_reply->type != REDIS_REPLY_INTEGER || this->_reply->integer <= 0) {
  11. std::cout << "Execut command [ LPUSH " << key << " " << value << " ] failure ! " << std::endl;
  12. freeReplyObject(this->_reply);
  13. return false;
  14. }
  15. std::cout << "Execut command [ LPUSH " << key << " " << value << " ] success ! " << std::endl;
  16. freeReplyObject(this->_reply);
  17. return true;
  18. }

左侧pop

  1. bool RedisMgr::LPop(const std::string &key, std::string& value){
  2. this->_reply = (redisReply*)redisCommand(this->_connect, "LPOP %s ", key.c_str());
  3. if (_reply == nullptr || _reply->type == REDIS_REPLY_NIL) {
  4. std::cout << "Execut command [ LPOP " << key<< " ] failure ! " << std::endl;
  5. freeReplyObject(this->_reply);
  6. return false;
  7. }
  8. value = _reply->str;
  9. std::cout << "Execut command [ LPOP " << key << " ] success ! " << std::endl;
  10. freeReplyObject(this->_reply);
  11. return true;
  12. }

右侧push

  1. bool RedisMgr::RPush(const std::string& key, const std::string& value) {
  2. this->_reply = (redisReply*)redisCommand(this->_connect, "RPUSH %s %s", key.c_str(), value.c_str());
  3. if (NULL == this->_reply)
  4. {
  5. std::cout << "Execut command [ RPUSH " << key << " " << value << " ] failure ! " << std::endl;
  6. freeReplyObject(this->_reply);
  7. return false;
  8. }
  9. if (this->_reply->type != REDIS_REPLY_INTEGER || this->_reply->integer <= 0) {
  10. std::cout << "Execut command [ RPUSH " << key << " " << value << " ] failure ! " << std::endl;
  11. freeReplyObject(this->_reply);
  12. return false;
  13. }
  14. std::cout << "Execut command [ RPUSH " << key << " " << value << " ] success ! " << std::endl;
  15. freeReplyObject(this->_reply);
  16. return true;
  17. }

右侧pop

  1. bool RedisMgr::RPop(const std::string& key, std::string& value) {
  2. this->_reply = (redisReply*)redisCommand(this->_connect, "RPOP %s ", key.c_str());
  3. if (_reply == nullptr || _reply->type == REDIS_REPLY_NIL) {
  4. std::cout << "Execut command [ RPOP " << key << " ] failure ! " << std::endl;
  5. freeReplyObject(this->_reply);
  6. return false;
  7. }
  8. value = _reply->str;
  9. std::cout << "Execut command [ RPOP " << key << " ] success ! " << std::endl;
  10. freeReplyObject(this->_reply);
  11. return true;
  12. }

HSet操作

  1. bool RedisMgr::HSet(const std::string &key, const std::string &hkey, const std::string &value) {
  2. this->_reply = (redisReply*)redisCommand(this->_connect, "HSET %s %s %s", key.c_str(), hkey.c_str(), value.c_str());
  3. if (_reply == nullptr || _reply->type != REDIS_REPLY_INTEGER ) {
  4. std::cout << "Execut command [ HSet " << key << " " << hkey <<" " << value << " ] failure ! " << std::endl;
  5. freeReplyObject(this->_reply);
  6. return false;
  7. }
  8. std::cout << "Execut command [ HSet " << key << " " << hkey << " " << value << " ] success ! " << std::endl;
  9. freeReplyObject(this->_reply);
  10. return true;
  11. }
  12. bool RedisMgr::HSet(const char* key, const char* hkey, const char* hvalue, size_t hvaluelen)
  13. {
  14. const char* argv[4];
  15. size_t argvlen[4];
  16. argv[0] = "HSET";
  17. argvlen[0] = 4;
  18. argv[1] = key;
  19. argvlen[1] = strlen(key);
  20. argv[2] = hkey;
  21. argvlen[2] = strlen(hkey);
  22. argv[3] = hvalue;
  23. argvlen[3] = hvaluelen;
  24. this->_reply = (redisReply*)redisCommandArgv(this->_connect, 4, argv, argvlen);
  25. if (_reply == nullptr || _reply->type != REDIS_REPLY_INTEGER) {
  26. std::cout << "Execut command [ HSet " << key << " " << hkey << " " << hvalue << " ] failure ! " << std::endl;
  27. freeReplyObject(this->_reply);
  28. return false;
  29. }
  30. std::cout << "Execut command [ HSet " << key << " " << hkey << " " << hvalue << " ] success ! " << std::endl;
  31. freeReplyObject(this->_reply);
  32. return true;
  33. }

HGet操作

  1. std::string RedisMgr::HGet(const std::string &key, const std::string &hkey)
  2. {
  3. const char* argv[3];
  4. size_t argvlen[3];
  5. argv[0] = "HGET";
  6. argvlen[0] = 4;
  7. argv[1] = key.c_str();
  8. argvlen[1] = key.length();
  9. argv[2] = hkey.c_str();
  10. argvlen[2] = hkey.length();
  11. this->_reply = (redisReply*)redisCommandArgv(this->_connect, 3, argv, argvlen);
  12. if (this->_reply == nullptr || this->_reply->type == REDIS_REPLY_NIL) {
  13. freeReplyObject(this->_reply);
  14. std::cout << "Execut command [ HGet " << key << " "<< hkey <<" ] failure ! " << std::endl;
  15. return "";
  16. }
  17. std::string value = this->_reply->str;
  18. freeReplyObject(this->_reply);
  19. std::cout << "Execut command [ HGet " << key << " " << hkey << " ] success ! " << std::endl;
  20. return value;
  21. }

Del 操作

  1. bool RedisMgr::Del(const std::string &key)
  2. {
  3. this->_reply = (redisReply*)redisCommand(this->_connect, "DEL %s", key.c_str());
  4. if (this->_reply == nullptr || this->_reply->type != REDIS_REPLY_INTEGER) {
  5. std::cout << "Execut command [ Del " << key << " ] failure ! " << std::endl;
  6. freeReplyObject(this->_reply);
  7. return false;
  8. }
  9. std::cout << "Execut command [ Del " << key << " ] success ! " << std::endl;
  10. freeReplyObject(this->_reply);
  11. return true;
  12. }

判断键值是否存在

  1. bool RedisMgr::ExistsKey(const std::string &key)
  2. {
  3. this->_reply = (redisReply*)redisCommand(this->_connect, "exists %s", key.c_str());
  4. if (this->_reply == nullptr || this->_reply->type != REDIS_REPLY_INTEGER || this->_reply->integer == 0) {
  5. std::cout << "Not Found [ Key " << key << " ] ! " << std::endl;
  6. freeReplyObject(this->_reply);
  7. return false;
  8. }
  9. std::cout << " Found [ Key " << key << " ] exists ! " << std::endl;
  10. freeReplyObject(this->_reply);
  11. return true;
  12. }

关闭

  1. void RedisMgr::Close()
  2. {
  3. redisFree(_connect);
  4. }

测试用例

  1. void TestRedisMgr() {
  2. assert(RedisMgr::GetInstance()->Connect("127.0.0.1", 6380));
  3. assert(RedisMgr::GetInstance()->Auth("123456"));
  4. assert(RedisMgr::GetInstance()->Set("blogwebsite","llfc.club"));
  5. std::string value="";
  6. assert(RedisMgr::GetInstance()->Get("blogwebsite", value) );
  7. assert(RedisMgr::GetInstance()->Get("nonekey", value) == false);
  8. assert(RedisMgr::GetInstance()->HSet("bloginfo","blogwebsite", "llfc.club"));
  9. assert(RedisMgr::GetInstance()->HGet("bloginfo","blogwebsite") != "");
  10. assert(RedisMgr::GetInstance()->ExistsKey("bloginfo"));
  11. assert(RedisMgr::GetInstance()->Del("bloginfo"));
  12. assert(RedisMgr::GetInstance()->Del("bloginfo"));
  13. assert(RedisMgr::GetInstance()->ExistsKey("bloginfo") == false);
  14. assert(RedisMgr::GetInstance()->LPush("lpushkey1", "lpushvalue1"));
  15. assert(RedisMgr::GetInstance()->LPush("lpushkey1", "lpushvalue2"));
  16. assert(RedisMgr::GetInstance()->LPush("lpushkey1", "lpushvalue3"));
  17. assert(RedisMgr::GetInstance()->RPop("lpushkey1", value));
  18. assert(RedisMgr::GetInstance()->RPop("lpushkey1", value));
  19. assert(RedisMgr::GetInstance()->LPop("lpushkey1", value));
  20. assert(RedisMgr::GetInstance()->LPop("lpushkey2", value)==false);
  21. RedisMgr::GetInstance()->Close();
  22. }

封装redis连接池

  1. class RedisConPool {
  2. public:
  3. RedisConPool(size_t poolSize, const char* host, int port, const char* pwd)
  4. : poolSize_(poolSize), host_(host), port_(port), b_stop_(false){
  5. for (size_t i = 0; i < poolSize_; ++i) {
  6. auto* context = redisConnect(host, port);
  7. if (context == nullptr || context->err != 0) {
  8. if (context != nullptr) {
  9. redisFree(context);
  10. }
  11. continue;
  12. }
  13. auto reply = (redisReply*)redisCommand(context, "AUTH %s", pwd);
  14. if (reply->type == REDIS_REPLY_ERROR) {
  15. std::cout << "认证失败" << std::endl;
  16. //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
  17. freeReplyObject(reply);
  18. continue;
  19. }
  20. //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
  21. freeReplyObject(reply);
  22. std::cout << "认证成功" << std::endl;
  23. connections_.push(context);
  24. }
  25. }
  26. ~RedisConPool() {
  27. std::lock_guard<std::mutex> lock(mutex_);
  28. while (!connections_.empty()) {
  29. connections_.pop();
  30. }
  31. }
  32. redisContext* getConnection() {
  33. std::unique_lock<std::mutex> lock(mutex_);
  34. cond_.wait(lock, [this] {
  35. if (b_stop_) {
  36. return true;
  37. }
  38. return !connections_.empty();
  39. });
  40. //如果停止则直接返回空指针
  41. if (b_stop_) {
  42. return nullptr;
  43. }
  44. auto* context = connections_.front();
  45. connections_.pop();
  46. return context;
  47. }
  48. void returnConnection(redisContext* context) {
  49. std::lock_guard<std::mutex> lock(mutex_);
  50. if (b_stop_) {
  51. return;
  52. }
  53. connections_.push(context);
  54. cond_.notify_one();
  55. }
  56. void Close() {
  57. b_stop_ = true;
  58. cond_.notify_all();
  59. }
  60. private:
  61. atomic<bool> b_stop_;
  62. size_t poolSize_;
  63. const char* host_;
  64. int port_;
  65. std::queue<redisContext*> connections_;
  66. std::mutex mutex_;
  67. std::condition_variable cond_;
  68. };

RedisMgr构造函数中初始化pool连接池

  1. RedisMgr::RedisMgr() {
  2. auto& gCfgMgr = ConfigMgr::Inst();
  3. auto host = gCfgMgr["Redis"]["Host"];
  4. auto port = gCfgMgr["Redis"]["Port"];
  5. auto pwd = gCfgMgr["Redis"]["Passwd"];
  6. _con_pool.reset(new RedisConPool(5, host.c_str(), atoi(port.c_str()), pwd.c_str()));
  7. }

在析构函数中回收资源

  1. RedisMgr::~RedisMgr() {
  2. Close();
  3. }
  4. void RedisMgr::Close() {
  5. _con_pool->Close();
  6. }

在使用的时候改为从Pool中获取链接

  1. bool RedisMgr::Get(const std::string& key, std::string& value)
  2. {
  3. auto connect = _con_pool->getConnection();
  4. if (connect == nullptr) {
  5. return false;
  6. }
  7. auto reply = (redisReply*)redisCommand(connect, "GET %s", key.c_str());
  8. if (reply == NULL) {
  9. std::cout << "[ GET " << key << " ] failed" << std::endl;
  10. freeReplyObject(reply);
  11. _con_pool->returnConnection(connect);
  12. return false;
  13. }
  14. if (reply->type != REDIS_REPLY_STRING) {
  15. std::cout << "[ GET " << key << " ] failed" << std::endl;
  16. freeReplyObject(reply);
  17. _con_pool->returnConnection(connect);
  18. return false;
  19. }
  20. value = reply->str;
  21. freeReplyObject(reply);
  22. std::cout << "Succeed to execute command [ GET " << key << " ]" << std::endl;
  23. _con_pool->returnConnection(connect);
  24. return true;
  25. }

总结

本节告诉大家如何搭建redis服务,linux和windows环境的,并且编译了windows版本的hredis库,解决了链接错误,而且封装了RedisMgr管理类。
并实现了测试用例,大家感兴趣可以测试一下。下一节实现VarifyServer访问的redis功能。

热门评论

热门文章

  1. 解密定时器的实现细节

    喜欢(566) 浏览(1881)
  2. C++ 类的继承封装和多态

    喜欢(588) 浏览(2609)
  3. slice介绍和使用

    喜欢(521) 浏览(1740)
  4. Linux环境搭建和编码

    喜欢(594) 浏览(5588)
  5. windows环境搭建和vscode配置

    喜欢(587) 浏览(1716)

最新评论

  1. C++ 类的拷贝构造、赋值运算、单例模式 secondtonone1:好的,已修复。
  2. 双链表实现LRU算法 secondtonone1:双链表插入和删除节点是本篇的难点,多多练习即可。
  3. 线程安全的无锁栈 secondtonone1:谢谢支持,如果pop的次数大于push的次数是会让线程处于重试的,这个是测试用例,必须满足push和pop的次数相同,实际情况不会这么使用。栈的设计没有问题。
  4. 再谈单例模式 secondtonone1:是的,C++11以后返回局部static变量对象能保证线程安全了。
  5. Linux环境搭建和编码 恋恋风辰:Linux环境下go的安装比较简单,可以不用设置GOPATH环境变量,后期我们学习go mod 之后就拜托了go文件目录的限制了。

个人公众号

个人微信