封装hiredis——C++与redis对接(一)(string的SET与GET操作)
在菜鸟教程自学了redis,总想着像Mysql一样,在C/C++中进行对接。于是查询了一些资料,最后找到了hiredis。然而直接用它的话,难免有点不方便。于是,对其进行封装。
hiredis直接去git上克隆,地址:https://github.com/redis/hiredis。
下载好之后,由于其自带Makefile,只要make一下就编译出静态库与动态库了,接着把头文件和静/动态库放在相应的文件夹里就可以了。注意如果使用动态库,而且是放在/usr/local/lib/里,得执行ldconfig命令,来更新一下配置,或者得配置一下动态库路径。
安装好了就是如何使用的事了。
学习hiredis主要是参考这两个链接:
http://blog.csdn.net/gqtcgq/article/details/51344232
http://blog.csdn.net/achelloworld/article/details/41598389?utm_source=tuicool&utm_medium=referral
一共就五个函数。
1、redisContext* redisConnect(const char *ip, int port)
2、redisContext* redisConnectWithTimeout(const char *ip, int port, timeval tv)
3、void redisFree(redisContext *c)
4、void *redisCommand(redisContext *c, const char *format...)
5、void freeReplyObject(void *reply)
和Mysql一样,要对接,第一件事就是用IP和端口号建立连接什么的。redis的端口号一般是6379,IP直接用127.0.0.1就可以了。既然要用到IP和端口号,又是可能会变的东西,为了不使想要改变它们的时候得直接修改代码,我写了个配置文件:
redisConf.json
1 { 2 "IP" : "127.0.0.1" , 3 "PORT" : 6379 4 }
相应地,有提取配置信息的类
redisConf.h
1 #ifndef __REDISCONF_H__ 2 #define __REDISCONF_H__ 3 #include <string> 4 namespace ccx{ 5 using std::string; 6 class RedisConf 7 { 8 public: 9 RedisConf(); 10 void getConf(); 11 string getIP(); 12 int getPort(); 13 private: 14 string _ip; 15 int _port; 16 }; 17 } 18 #endif
redisconf.cc
1 #include "redisConf.h" 2 #include <stdlib.h> 3 #include <json/json.h> 4 #include <string> 5 #include <iostream> 6 #include <fstream> 7 8 namespace ccx{ 9 10 using std::ifstream; 11 using std::cout; 12 using std::endl; 13 14 RedisConf::RedisConf() 15 { 16 getConf(); 17 } 18 19 void RedisConf::getConf() 20 { 21 ifstream ifs; 22 ifs.open("redisConf.json"); 23 if(!ifs.good()) 24 { 25 cout << "open RedisConf.json error" << endl; 26 exit(EXIT_FAILURE); 27 } 28 29 Json::Value root; 30 Json::Reader reader; 31 if(!reader.parse(ifs, root, false)) 32 { 33 cout << "RedisConf json reader error" << endl; 34 exit(EXIT_FAILURE); 35 } 36 37 _ip = root["IP"].asString(); 38 _port = root["PORT"].asInt(); 39 ifs.close(); 40 } 41 42 string RedisConf::getIP() 43 { 44 return _ip; 45 } 46 47 int RedisConf::getPort() 48 { 49 return _port; 50 } 51 52 }
然后是目前的redis类:
redis.h
1 #ifndef __REDIS_H__ 2 #define __REDIS_H__ 3 4 #include "redisConf.h" 5 6 #include <hiredis/hiredis.h> 7 8 9 namespace ccx{ 10 11 class Redis 12 { 13 public: 14 Redis(); 15 public: 16 void Connect(); 17 void disConnect(); 18 public: 19 void setString(const string & key, const string & value); 20 void setString(const string & key, const int & value); 21 void setString(const string & key, const float & value); 22 private: 23 void setString(const string & data); 24 public: 25 void getString(const string & key, string & value); 26 void getString(const string & key, int & value); 27 void getString(const string & key, float & value); 28 private: 29 void getString(const string & key); 30 private: 31 void freeReply(); 32 bool isError(); 33 private: 34 RedisConf _conf; 35 redisContext * _context; 36 redisReply * _reply; 37 }; 38 } 39 40 #endif
下面结合写好的代码说说前面的五个函数。
函数1是用来连接redis的,具体如下:
1 void Redis::Connect() 2 { 3 _context = ::redisConnect(_conf.getIP().c_str(), _conf.getPort()); 4 cout << _conf.getIP() << "-" << _conf.getPort() << endl; 5 if(_context && _context->err) 6 { 7 cout << "connect redis error" << endl; 8 exit(EXIT_FAILURE); 9 } 10 cout << "redis Connect success" << endl; 11 }
函数2是在1的基础上,添加了一个超时功能。
函数3是在不使用redis了,要断开连接时使用的:
1 void Redis::disConnect() 2 { 3 ::redisFree(_context); 4 cout << "redis disConnect success" << endl; 5 }
函数4稍微复杂一些,有点像C中的printf:
1 printf("%d%s%d",d1,s1,d2); 2 printf("hello,world");
可以这样用:
1 char * command = "SET name lili"; 2 reply = (redisReply*)::redisCommand(context, command); 3 char * s1 = "name"; 4 char * s2 = "lili"; 5 reply = (redisReply*)::redisCommand(context, "SET %s %s", s1, s2); 6 reply = (redisReply*)::redisCommand(context, "SET name lili");
7 ...
第一个参数context是函数1或者2的返回值,告诉它要与哪里的redis进行交互。reply指向命令结果的存储位置。
函数5是用来清理函数4 的返回结果的:
1 void Redis::freeReply() 2 { 3 if(_reply) 4 { 5 ::freeReplyObject(_reply); 6 _reply = NULL; 7 } 8 }
第6行是因为对这个函数不熟,就干脆清完之后给它赋值NULL。
由于redis的string中存的可能是字符串、整形、浮点数,于是各自重载了三个版本的get与set方法,并重用一些函数,以减少代码量。
对于set,直接用一个宏替换:
1 #define SETSTRING(key, value) \ 2 stringstream ss;\ 3 ss << "SET " << key << " " << value;\ 4 string s;\ 5 getline(ss, s);\ 6 setString(s);
1 void Redis::setString(const string & key, const string & value) 2 { 3 SETSTRING(key, value); 4 } 5 void Redis::setString(const string & key, const int & value) 6 { 7 SETSTRING(key, value); 8 } 9 void Redis::setString(const string & key, const float & value) 10 { 11 SETSTRING(key, value); 12 }
使用C++中的stringstream,会比用“%d”、“%s”、“%f”来区分类型少些代码。两种方法的结果是相同的。
它们共用的setString方法:
1 void Redis::setString(const string & data) 2 { 3 freeReply(); 4 _reply = (redisReply*)::redisCommand(_context, data.c_str()); 5 if(!isError()) 6 { 7 if (!(_reply->type == REDIS_REPLY_STATUS && strcasecmp(_reply->str,"OK") == 0)) 8 { 9 cout << "Failed to execute SET(string)" << endl; 10 } 11 } 12 }
这里的isError是用来判断是否连接异常的:
1 bool Redis::isError() 2 { 3 if(NULL == _reply) 4 { 5 freeReply(); 6 disConnect(); 7 Connect(); 8 return true; 9 } 10 return false; 11 }
如果连接异常,得断开重连。
在redis命令行里,如果set成功,会提示“OK”。于是,这里先判断了一下命令结果的数据类型,如果是字符串,再判断它是不是“OK”,以此来判断set是否成功。
对于get,我试了各种方法,都无法直接从命令结果中提取出数字,暂时还没找到原因。但是数字却可以以字符串格式得到。于是,使用了atoi来处理:
1 void Redis::getString(const string & key) 2 { 3 freeReply(); 4 _reply = (redisReply*)::redisCommand(_context, "GET %s", key.c_str()); 5 } 6 7 void Redis::getString(const string & key, string & value) 8 { 9 getString(key); 10 if(!isError() && _reply->type == REDIS_REPLY_STRING) 11 { 12 value = _reply->str; 13 } 14 } 15 16 void Redis::getString(const string & key, int & value) 17 { 18 getString(key); 19 if(!isError() && _reply->type == REDIS_REPLY_STRING) 20 { 21 value = ::atoi(_reply->str); 22 } 23 } 24 25 void Redis::getString(const string & key, float & value) 26 { 27 getString(key); 28 if(!isError() && _reply->type == REDIS_REPLY_STRING) 29 { 30 value = ::atof(_reply->str); 31 } 32 }
redis.cc
1 #include "redis.h" 2 3 #include <string.h> 4 #include <stdlib.h> 5 6 #include <sstream> 7 #include <iostream> 8 9 namespace ccx{ 10 11 using std::cout; 12 using std::endl; 13 using std::stringstream; 14 15 #define SETSTRING(key, value) \ 16 stringstream ss;\ 17 ss << "SET " << key << " " << value;\ 18 string s;\ 19 getline(ss, s);\ 20 setString(s); 21 22 Redis::Redis() 23 : _conf() 24 { 25 } 26 27 void Redis::Connect() 28 { 29 _context = ::redisConnect(_conf.getIP().c_str(), _conf.getPort()); 30 cout << _conf.getIP() << "-" << _conf.getPort() << endl; 31 if(_context && _context->err) 32 { 33 cout << "connect redis error" << endl; 34 exit(EXIT_FAILURE); 35 } 36 cout << "redis Connect success" << endl; 37 } 38 39 void Redis::disConnect() 40 { 41 ::redisFree(_context); 42 cout << "redis disConnect success" << endl; 43 } 44 45 void Redis::setString(const string & data) 46 { 47 freeReply(); 48 _reply = (redisReply*)::redisCommand(_context, data.c_str()); 49 if(!isError()) 50 { 51 if (!(_reply->type == REDIS_REPLY_STATUS && strcasecmp(_reply->str,"OK") == 0)) 52 { 53 cout << "Failed to execute SET(string)" << endl; 54 } 55 } 56 } 57 58 void Redis::setString(const string & key, const string & value) 59 { 60 SETSTRING(key, value); 61 } 62 63 void Redis::setString(const string & key, const int & value) 64 { 65 SETSTRING(key, value); 66 } 67 68 void Redis::setString(const string & key, const float & value) 69 { 70 SETSTRING(key, value); 71 } 72 73 void Redis::getString(const string & key) 74 { 75 freeReply(); 76 _reply = (redisReply*)::redisCommand(_context, "GET %s", key.c_str()); 77 } 78 79 void Redis::getString(const string & key, string & value) 80 { 81 getString(key); 82 if(!isError() && _reply->type == REDIS_REPLY_STRING) 83 { 84 value = _reply->str; 85 } 86 } 87 88 void Redis::getString(const string & key, int & value) 89 { 90 getString(key); 91 if(!isError() && _reply->type == REDIS_REPLY_STRING) 92 { 93 value = ::atoi(_reply->str); 94 } 95 } 96 97 void Redis::getString(const string & key, float & value) 98 { 99 getString(key); 100 if(!isError() && _reply->type == REDIS_REPLY_STRING) 101 { 102 value = ::atof(_reply->str); 103 } 104 } 105 106 void Redis::freeReply() 107 { 108 if(_reply) 109 { 110 ::freeReplyObject(_reply); 111 _reply = NULL; 112 } 113 } 114 115 bool Redis::isError() 116 { 117 if(NULL == _reply) 118 { 119 freeReply(); 120 disConnect(); 121 Connect(); 122 return true; 123 } 124 return false; 125 } 126 127 }
test.cc
1 #include "redis.h" 2 3 #include <string> 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 using std::string; 9 10 int main() 11 { 12 ccx::Redis redis; 13 redis.Connect(); 14 redis.setString("name", "lii"); 15 16 string s; 17 redis.getString("name", s); 18 cout << s << endl; 19 20 redis.setString("age", "30"); 21 redis.getString("age", s); 22 cout << s << endl; 23 24 int i; 25 redis.getString("age", i); 26 cout << i << endl; 27 28 redis.disConnect(); 29 }
测试结果如下:
127.0.0.1-6379 redis Connect success lii 30 30 redis disConnect success