Redis C客户端库Hiredis的应用
Redis
Redis(REmote DIctionary Server)是一个高性能的key-value数据库。
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
Redis的使用这里不做说明,请参考如下网址:
http://redisdoc.com/
http://www.runoob.com/redis/redis-tutorial.html
Hiredis
Hiredis是Redis数据库的一个极简C客户端库,只是对Redis协议的最小支持。
源码地址:https://github.com/redis/hiredis
1. 同步接口
(1)建立连接:与Redis server建立连接,返回一个redisContext结构指针
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
(2)发送命令:向Redis server发送命令;成功:返回redisReply结构指针,失败:返回NULL
// 同步执行redis命令,和printf()用法类似 void *redisCommand(redisContext *c, const char *format, ...); // argc:argv数组元素个数;argv:参数数组(指针数组);argvlen:数组首地址,每个元素是argv数组中相应参数的长度。 // 传入命令是字符串形式时,argvlen可以指定为NULL,这个时候使用strlen()计算argv中每个字符串长度;传入命令是二进制形式时,argvlen必须指定,用于指示argv中每个元素的长度 void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
(3)释放redisReply结构指针
void freeReplyObject(void *reply);
(4)释放redisContext结构指针
void redisFree(redisContext *c);
2. 主要数据结构
(1)redisContext:管理连接上下文的结构体,由redisConnect()函数创建并返回。
/* Context for a connection to Redis */ typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ int fd; int flags; char *obuf; /* Write buffer */ redisReader *reader; /* Protocol reader */ enum redisConnectionType connection_type; struct timeval *timeout; struct { char *host; char *source_addr; int port; } tcp; struct { char *path; } unix_sock; /* For non-blocking connect */ struct sockadr *saddr; size_t addrlen; } redisContext;
(2)redisReply:响应结构体,由redisCommand()函数创建并返回。
/* This is the reply object returned by redisCommand() */ typedef struct redisReply { int type; /* REDIS_REPLY_* */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ size_t len; /* Length of string */ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ } redisReply;
redisReply结构体中的type字段有如下值:
#define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 #define REDIS_REPLY_INTEGER 3 #define REDIS_REPLY_NIL 4 #define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_ERROR 6
3. 示例代码
1. Hiredis源码中提供的一个例子:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <hiredis.h> int main(int argc, char **argv) { unsigned int j, isunix = 0; redisContext *c; redisReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; if (argc > 2) { if (*argv[2] == 'u' || *argv[2] == 'U') { isunix = 1; /* in this case, host is the path to the unix socket */ printf("Will connect to unix socket @%s\n", hostname); } } int port = (argc > 2) ? atoi(argv[2]) : 6379; struct timeval timeout = { 1, 500000 }; // 1.5 seconds if (isunix) { c = redisConnectUnixWithTimeout(hostname, timeout); } else { c = redisConnectWithTimeout(hostname, port, timeout); } if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); redisFree(c); } else { printf("Connection error: can't allocate redis context\n"); } exit(1); } /* PING server */ reply = redisCommand(c,"PING"); printf("PING: %s\n", reply->str); freeReplyObject(reply); /* Set a key */ reply = redisCommand(c,"SET %s %s", "foo", "hello world"); printf("SET: %s\n", reply->str); freeReplyObject(reply); /* Set a key using binary safe API */ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); /* Try a GET and two INCR */ reply = redisCommand(c,"GET foo"); printf("GET foo: %s\n", reply->str); freeReplyObject(reply); reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* again ... */ reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* Create a list of numbers, from 0 to 9 */ reply = redisCommand(c,"DEL mylist"); freeReplyObject(reply); for (j = 0; j < 10; j++) { char buf[64]; snprintf(buf,64,"%u",j); reply = redisCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } /* Let's check what we have inside the list */ reply = redisCommand(c,"LRANGE mylist 0 -1"); if (reply->type == REDIS_REPLY_ARRAY) { for (j = 0; j < reply->elements; j++) { printf("%u) %s\n", j, reply->element[j]->str); } } freeReplyObject(reply); /* Disconnects and frees the context */ redisFree(c); return 0; }
2. 一个C++封装的示例
//RedisClient.h #pragma once #include <string> #include <queue> #include <vector> #include <hiredis/hiredis.h> #include <mutex> #include <boost/shared_ptr.hpp> class RedisClient { public: RedisClient(std::string ip, int port, int timeout = 2000); virtual ~RedisClient(); bool set(const std::string &key, const std::string& value); bool get(const std::string &key, std::string& value); bool rpush(const std::string &key, const std::string& value); bool lpop(const std::string &key, std::string& value); bool hget(const std::string &key, const std::string& field, std::string& value); bool hget(const std::string &key, int field, std::string& value); bool lrange(const std::string& key, int start, int end, std::vector<std::string>& values); bool zrange(const std::string& key, int start, int end, std::vector<std::string>& values); private: int m_timeout; int m_serverPort; std::string m_setverIp; std::mutex m_mutex; std::queue<redisContext *> m_clients; time_t m_beginInvalidTime; static const int m_maxReconnectInterval = 3; redisContext* CreateContext(); void ReleaseContext(redisContext *ctx, bool active); bool CheckStatus(redisContext *ctx); }; typedef boost::shared_ptr<RedisClient> RedisClientPtr; typedef boost::shared_ptr<const RedisClient> const_RedisClientPtr; //RedisClient.cpp #include "RedisClient.h" #include <memory> #include <string.h> #include <iostream> #include <vector> using namespace std; RedisClient::RedisClient(string ip, int port, int timeout) { m_timeout = timeout; m_serverPort = port; m_setverIp = ip; m_beginInvalidTime = 0; } RedisClient::~RedisClient() { lock_guard<mutex> lock(m_mutex); while(!m_clients.empty()) { redisContext *ctx = m_clients.front(); redisFree(ctx); m_clients.pop(); } } bool RedisClient::get(const string& key, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "GET %s", key.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::set(const string& key, const string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "SET %s %s", key.c_str(), value.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if( !(r->type == REDIS_REPLY_STATUS && strcasecmp(r->str,"OK")==0)) { return false; } return true; } bool RedisClient::rpush(const string& key, const string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "RPUSH %s %s", key.c_str(), value.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if(r->type != REDIS_REPLY_INTEGER) { return false; } return true; } bool RedisClient::lpop(const string& key, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "LPOP %s", key.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::lrange(const std::string& key, int start, int end, vector<string>& values) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "LRANGE %s %d %d", key.c_str(), start, end); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_ARRAY) { return false; } for (int i = 0; i < (int)r->elements; i++) { redisReply *cur = r->element[i]; if (cur->type != REDIS_REPLY_STRING) { return false; } values.push_back(cur->str); } return true; } redisContext* RedisClient::CreateContext() { { lock_guard<mutex> lock(m_mutex); if(!m_clients.empty()) { redisContext *ctx = m_clients.front(); m_clients.pop(); return ctx; } } time_t now = time(NULL); if(now < m_beginInvalidTime + m_maxReconnectInterval) return NULL; struct timeval tv; tv.tv_sec = m_timeout / 1000; tv.tv_usec = (m_timeout % 1000) * 1000;; redisContext *ctx = redisConnectWithTimeout(m_setverIp.c_str(), m_serverPort, tv); if(ctx == NULL || ctx->err != 0) { if(ctx != NULL) redisFree(ctx); m_beginInvalidTime = time(NULL); return NULL; } return ctx; } void RedisClient::ReleaseContext(redisContext *ctx, bool active) { if(ctx == NULL) return; if(!active) {redisFree(ctx); return;} lock_guard<mutex> lock(m_mutex); m_clients.push(ctx); } bool RedisClient::CheckStatus(redisContext *ctx) { redisReply *reply = (redisReply*)redisCommand(ctx, "ping"); if(reply == NULL) return false; shared_ptr<redisReply> autoFree(reply, freeReplyObject); if(reply->type != REDIS_REPLY_STATUS) return false; if(strcasecmp(reply->str,"PONG") != 0) return false; return true; } bool RedisClient::hget(const string& key, int field, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "HGET %s %d", key.c_str(), field); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::hget(const string& key, const string& field, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "HGET %s %s", key.c_str(), field.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::zrange(const std::string& key, int start, int end, vector<string>& values) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "ZRANGE %s %d %d", key.c_str(), start, end); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_ARRAY) { return false; } for (int i = 0; i < (int)r->elements; i++) { redisReply *cur = r->element[i]; if (cur->type != REDIS_REPLY_STRING) { return false; } values.push_back(cur->str); } return true; }