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;
}

View Code