Hiredis是一个Redis的C客户端库函数,基本实现了Redis的协议的最小集。这里对hiredis的api作基本的介绍以及应用,主要参考hiredis的README文件以及相关源码。

1、Ubuntu安装redis服务端

Redis全称为Remote Dictionary Server(远程数据服务),是一款开源的基于内存的键值对存储系统,其主要被用作高性能缓存服务器使用,当然也可以作为消息中间件和Session共享等。Redis独特的键值对模型使之支持丰富的数据结构类型,即它的值可以是字符串、哈希、列表、集合、有序集合,而不像Memcached要求的键和值都是字符串。同时由于Redis是基于内存的方式,免去了磁盘I/O速度的影响,因此其读写性能极高。

1、在Ubuntu中打开终端,输入下列命令,下载Redis安装包:

wget http://download.redis.io/releases/redis-4.0.9.tar.gz
  • 1.

2、对安装包进行解压,并将其移动放到usr/local⽬录下 命令如下:解压:

tar xzf redis-4.0.9.tar.gz
移动到
sudo mv ./redis-4.0.9 /usr/local/redis/
  • 1.
  • 2.
  • 3.

3、进入redis⽬录,编译生成 命令:

cd /usr/local/redis/
sudo make

测试
sudo make test
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

4、安装,将redis的命令安装到/usr/local/bin/⽬录

sudo make install
  • 1.

5、安装完成后,我们进入目录/usr/local/bin中查看

cd /usr/local/bin
ls -all
  • 1.
  • 2.

6、配置⽂件,移动到/etc/⽬录下 配置⽂件⽬录为/usr/local/redis/redis.conf

sudo cp /usr/local/redis/redis.conf /etc/redis/
  • 1.

7、Redis的配置信息在/etc/redis/redis.conf下

sudo vi /etc/redis/redis.conf

绑定ip:如果需要远程访问,可将此⾏注释,或绑定⼀个真实ip
bind 127.0.0.1

端⼝,默认为6379
port 6379

是否以守护进程运⾏
如果以守护进程运⾏,则不会在命令⾏阻塞,类似于服务
如果以⾮守护进程运⾏,则当前终端被阻塞
设置为yes表示守护进程,设置为no表示⾮守护进程
推荐设置为yes
daemonize yes

数据⽂件
dbfilename dump.rdb

数据⽂件存储路径
dir /var/lib/redis

⽇志⽂件
logfile "/var/log/redis/redis-server.log"

数据库,默认有16个
database 16

主从复制,类似于双机备份。
slaveof
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

连接服务端

./redis-cli -h 127.0.0.1 -p 6379

2、hiredis库的安装

官网:https://redislabs.com/lp/hiredis/ 发行版本:https://github.com/redis/hiredis/releases 目前最新的版本:https://codeload.github.com/redis/hiredis/tar.gz/v0.14.0

1、解压:tar -zxvf hiredis-0.14.0.tar.gz 2、编译:make 3、安装:make install

也可以直接将文件编译到自己的工程代码。

3、同步API接口的使用

我们的项目中使用的hireds接口都是同步的API,所谓同步意思就是使用阻塞的方式向redis server下发消息。接口的主要部分为下面三个部分,下面分别介绍。

/**连接数据库*/
redisContext *redisConnect(const char *ip, int port);
/**发送命令请求*/
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
/*释放资源*/
void freeReplyObject(void *reply);
void redisFree(redisContext *c);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

3.1、连接redis数据库redisConnect

redisContext *redisConnect(const char *ip, int port);
  • 1.

参数说明 ● port:为redis数据监听的端口号,redis默认监听的端口号为6379 ● ip:为redis数据库的IP地址,可以是远程的,也可以是本地的127.0.0.1 返回值 返回值是一个指向redisContext对象,可以不用了解这个对象的具体组成部分,只需要知道怎么使用就可以了。下面是其定义。

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;
} redisContext;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

3.2、发送需要执行的命令redisCommand

void *redisCommand(redisContext *c, const char *format, ...);
  • 1.

参数说明 这个函数是一个带有不定参数的。可以按着format格式给出对应的参数,这就和printf函数类似。c 是一个reidsConnect函数返回的一个对象。返回值 返回值是一个void类型的指针,实际为指向一个redisReply类型的指针。

/* This is the reply object returned by redisCommand() */
typedefstruct redisReply {
/*命令执行结果的返回类型*/
int type;/* REDIS_REPLY_* */
/*存储执行结果返回为整数*/
longlong 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;

返回结果的类型reply->type,reply 为redisReply*类型。
● REDIS_REPLY_STRING ==1:返回值是字符串,字符串储存在redis->str当中,字符串长度为redis->len。
● REDIS_REPLY_ARRAY ==2:返回值是数组,数组大小存在redis->elements里面,数组值存储在redis->element[i]里面。数组里面存储的是指向redisReply的指针,数组里面的返回值可以通过redis->element[i]->str来访问,数组的结果里全是type==REDIS_REPLY_STRING的redisReply对象指针。
● REDIS_REPLY_INTEGER ==3:返回值为整数longlong。
● REDIS_REPLY_NIL==4:返回值为空表示执行结果为空。
● REDIS_REPLY_STATUS ==5:返回命令执行的状态,比如set foo bar 返回的状态为OK,存储在str当中 reply->str =="OK"。
● REDIS_REPLY_ERROR ==6:命令执行错误,错误信息存放在 reply->str当中。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

3.3、redisCommandArgv函数

void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
  • 1.

参数说明 argvlen这个数组存储了命令参数中,每一个参数的长度,包含命令本身,比如 set foo bar 则argvlen ={3,3,3},如果argvlen为空,那么这个函数内部会自动调用strlen函数对每个参数进行求长度。argv 存放每个命令参数的指针,argv={"set","foo","bar"} argc 存放命令参数的个数上面的例子中argc=3 c 为redisContext对象。为每一个参数指定长度,可以是二进制安全的函数。函数会按着长度来决定字符串的终止,而不是'\0'.

char hkey[]="123456";
char hset[]="hset";
char key[]="testkey";
char hvalue[]="3210";
int argc =4;
char*argv[]={hset,key,hkey,hvalue};
size_t argvlen[]={4,6,4,3};
redisCommandArgv(context,argc,argv,argvlen);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

hgetall testkey 会得到321并不会得到和hvalue一样的值"3210",因为在hset命令中指定了长度,只会读取前面的三个字符。

3.4、redisAppendCommand*函数支持管道命令

void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
int redisGetReply(redisContext *context,redisReply** reply);
  • 1.
  • 2.
  • 3.

参数说明:redisAppendCommand函数和redisCommand函数参数一致,format可以指定特定参数的类型。c 为redisContext对象 redisAppendCommandArgv函数和redisCommandArgv函数类似,参数含义也相同。redisGetReply函数用来获得执行的结果的一条返回,并存储在reply所指的对象当中。成功返回REDIS_OK,否则返回REIDS_ERR。多条命令的一次性返回结果都存放在redisContext里面。所不同的是,这个两个命令的结果。这两个函数是把多个命令存放在缓冲区内,然后一起发送给redis服务器,一次执行。可以通过redisGetReply函数从 redisContext中取出返回的结果。使用例子:

redisReply *reply;
/*添加命令set */
redisAppendCommand(context,"SET foo bar");
/*添加命令get */
redisAppendCommand(context,"GET foo");
/*获取set命令结果*/
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
/*获取get命令结果*/
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

3.5、释放资源

void freeReplyObject(void *reply);
void redisFree(redisContext *c);
  • 1.
  • 2.

参数说明 freeReplyObject函数中reply 实际为指向redisReply结构体的指针,可能是redisCommand的返回值,后续可以看到以也能是管道命令执行结果的返回值。redisFree函数中c实际为指向redisContext对象,这个函数会清理连接资源并释放连接。

3.6、同步连接代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "hiredis/hiredis.h"

intmain(int argc,char *argv[])
{
unsignedint j,isunix =0;
    redisContext *c;
    redisReply *reply;
constchar*hostname ="127.0.0.1";
int port =6379;

struct timeval timeout ={1,500000};// 1.5 seconds
    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);
}

int num =1000;
for(int i=0;i<num;i++){
// INCR counter 是 Redis 的一个命令,它用于将名为 "counter" 的键的值递增1。如果该键不存在,它将被创建并初始化为0
        reply = redisCommand(c,"INCR counter");
printf("INCR counter:%lld\n",reply->integer);
        freeReplyObject(reply);
}
    redisFree(c);
return0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

如果执行的时候报这个错误

Redis的C客户端(hiredis库)使用_redis

在这里插入图片描述

解决办法:确保你的/etc/ld.so.conf里面有 /usr/local/lib 这一行 没有的话vim编辑在尾行加上 然后 sudo ldconfig

2、 ldconfig介绍 ldconfig是一个动态链接库管理命令,其目的为了让动态链接库为系统所共享。当进程需要链接相应的库文件时候,会默认搜寻/lilb和/usr/lib,以及配置文件/etc/ld.so.conf内所列的目录下的库文件。若找不到的话,就会出现如上图的错误。ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令。ldconfig需要注意的地方:

往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf文件的,但是添加完后需要调用下ldconfig,不然添加的library会找不到。

如果添加的library不在/lib和/usr/lib里面的话,就一定要修改/etc/ld.so.conf文件,往该文件追加library所在的路径,然后也需要重新调用下ldconfig命令。比如在安装MySQL的时候,其库文件/usr/local/mysql/lib,就需要追加到/etc/ld.so.conf文件中。命令如下:

echo "/usr/local/mysql/lib" >> /etc/ld.so.conf

ldconfig -v | grep mysql

最后注意:
如果添加的library不在/lib或/usr/lib下,但是却没有权限操作写/etc/ld.so.conf文件的话,这时就需要往export里写一个全局变量LD_LIBRARY_PATH,就可以了。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

3.7、异步连接

#include "hiredis/hiredis.h"
#include "hiredis/async.h"
#include "reactor.h"
#include "adapter.h"
#include <time.h>

staticreactor_t*R;
staticint cnt, before, num;


intcurrent_tick(){
int t =0;
struct timespec ti;
    clock_gettime(CLOCK_MONOTONIC,&ti);
    t =(int)ti.tv_sec *1000;
    t += ti.tv_nsec /1000000;
return t;
}

voidgetCallback(redisAsyncContext *c, void *r, void *privdata){
    redisReply *reply = r;
if(reply ==NULL)return;
printf("argv[%s]: %lld\n",(char*)privdata, reply->integer);

/* Disconnect after receiving the reply to GET */
    cnt++;
if(cnt == num){
int used = current_tick()-before;
printf("after %d exec redis command, used %d ms\n", num, used);
        redisAsyncDisconnect(c);
}
}


voidconnectCallback(const redisAsyncContext *c, int status){
if(status != REDIS_OK){
printf("Error: %s\n", c->errstr);
        stop_eventloop(R);
return;
}

printf("Connected...\n");
}

voiddisconnectCallback(const redisAsyncContext *c, int status){
if(status != REDIS_OK){
printf("Error: %s\n", c->errstr);
        stop_eventloop(R);
return;
}

printf("Disconnected...\n");
    stop_eventloop(R);
}

intmain(int argc, char **argv){
    redisAsyncContext *c = redisAsyncConnect("127.0.0.1",6379);
if(c->err){
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return1;
}
    R = create_reactor();

    redisAttach(R, c);

    redisAsyncSetConnectCallback(c, connectCallback);
    redisAsyncSetDisconnectCallback(c, disconnectCallback);

    before = current_tick();
    num =(argc >1)? atoi(argv[1]):1000;

for(int i =0; i < num; i++){
        redisAsyncCommand(c, getCallback,"count","INCR counter");
}

    eventloop(R);

    release_reactor(R);
return0;
}

// gcc main.c -o main -L./hiredis -lhiredis
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.

4、redis连接池实现

1、Thread.h头文件

#ifndef __THREAD_H__
#define __THREAD_H__
#include <stdint.h>
#include <pthread.h>

class CThreadNotify
{
public:
CThreadNotify()
{
        pthread_mutexattr_init(&m_mutexattr);
        pthread_mutexattr_settype(&m_mutexattr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&m_mutex,&m_mutexattr);

        pthread_cond_init(&m_cond,NULL);
}
~CThreadNotify()
{
        pthread_mutexattr_destroy(&m_mutexattr);
        pthread_mutex_destroy(&m_mutex);
        pthread_cond_destroy(&m_cond);
}
voidLock()
{
        pthread_mutex_lock(&m_mutex);
}
voidUnlock()
{
        pthread_mutex_unlock(&m_mutex);
}
voidWait()
{
        pthread_cond_wait(&m_cond,&m_mutex);
}
// 返回0则正常,其他值为异常
intWaitTime(int ms)
{
//获取时间
struct timespec outtime;
        clock_gettime(CLOCK_MONOTONIC,&outtime);
//ms为毫秒,换算成秒
        outtime.tv_sec += ms /1000;

//在outtime的基础上,增加ms毫秒
//outtime.tv_nsec为纳秒,1微秒=1000纳秒
//tv_nsec此值再加上剩余的毫秒数 ms%1000,有可能超过1秒。需要特殊处理
uint64_t us = outtime.tv_nsec /1000+1000*(ms %1000);//微秒
//us的值有可能超过1秒,
        outtime.tv_sec += us /1000000;

        us = us %1000000;
        outtime.tv_nsec = us *1000;//换算成纳秒
return pthread_cond_timedwait(&m_cond,&m_mutex,&outtime);
}
voidSignal()
{
        pthread_cond_signal(&m_cond);
}

private:
pthread_mutex_t m_mutex;
pthread_mutexattr_t m_mutexattr;

pthread_cond_t m_cond;
};

#endif
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.

2、头文件CachePool.h

/*
 * @Author: your name
 * @Date: 2019-12-07 10:54:57
 * @LastEditTime : 2020-01-10 16:35:13
 * @LastEditors  : Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \src\cache_pool\CachePool.h
 */
#ifndef CACHEPOOL_H_
#define CACHEPOOL_H_

#include <iostream>
#include <vector>
#include <map>
#include <list>

#include "Thread.h"

#include "hiredis.h"


usingstd::string;
usingstd::list;
usingstd::map;
usingstd::vector;

class CachePool;

class CacheConn {
public:
CacheConn(constchar* server_ip,int server_port,int db_index,constchar* password,
constchar*pool_name ="");
CacheConn(CachePool* pCachePool);
virtual~CacheConn();

intInit();
voidDeInit();
constchar*GetPoolName();
// 通用操作
// 判断一个key是否存在
boolisExists(string &key);
// 删除某个key
longdel(string &key);

// ------------------- 字符串相关 -------------------
stringget(string key);
stringset(string key, string& value);
stringsetex(string key, int timeout, string value);

// string mset(string key, map);
//批量获取
boolmget(const vector<string>& keys, map<string, string>& ret_value);
//原子加减1
longincr(string key);
longdecr(string key);


// ---------------- 哈希相关 ------------------------
longhdel(string key, string field);
stringhget(string key, string field);
boolhgetAll(string key, map<string, string>& ret_value);
longhset(string key, string field, string value);

longhincrBy(string key, string field, long value);
longincrBy(string key, long value);
stringhmset(string key, map<string, string>& hash);
boolhmget(string key, list<string>& fields, list<string>& ret_value);



// ------------ 链表相关 ------------
longlpush(string key, string value);
longrpush(string key, string value);
longllen(string key);
boollrange(string key, long start, long end, list<string>& ret_value);


boolflushdb();

private:
CachePool*   m_pCachePool;
    redisContext*  m_pContext;
uint64_t  m_last_connect_time;
uint16_t   m_server_port;
string    m_server_ip;
string          m_password;
uint16_t        m_db_index;
string    m_pool_name;
};


class CachePool {
public:
// db_index和mysql不同的地方 
CachePool(constchar* pool_name,constchar* server_ip,int server_port,int db_index,
constchar*password,int max_conn_cnt);
virtual~CachePool();

intInit();
// 获取空闲的连接资源
CacheConn*GetCacheConn();
// Pool回收连接资源
voidRelCacheConn(CacheConn* pCacheConn);

constchar*GetPoolName(){return m_pool_name.c_str();}
constchar*GetServerIP(){return m_server_ip.c_str();}
constchar*GetPassword(){return m_password.c_str();}
intGetServerPort(){return m_server_port;}
intGetDBIndex(){return m_db_index;}
private:
string   m_pool_name;
string  m_server_ip;
string   m_password;
int   m_server_port;
int   m_db_index;// mysql 数据库名字, redis db index

int   m_cur_conn_cnt;
int   m_max_conn_cnt;
list<CacheConn*> m_free_list;
CThreadNotify  m_free_notify;
};



#endif /* CACHEPOOL_H_ */
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.

3、CachePool.cpp文件

#include "CachePool.h"

#include <stdlib.h>
#include <string.h>
#include "Thread.h"

#define log_error printf
#define log_info printf

#define MIN_CACHE_CONN_CNT 2
#define MAX_CACHE_CONN_FAIL_NUM 10

CacheConn::CacheConn(constchar*server_ip,int server_port,int db_index,constchar*password,
constchar*pool_name)
{
    m_server_ip = server_ip;
    m_server_port = server_port;

    m_db_index = db_index;
    m_password = password;
    m_pool_name = pool_name;
    m_pContext =NULL;
    m_last_connect_time =0;
}

CacheConn::CacheConn(CachePool*pCachePool)
{
    m_pCachePool = pCachePool;
if(pCachePool)
{
        m_server_ip = pCachePool->GetServerIP();
        m_server_port = pCachePool->GetServerPort();
        m_db_index = pCachePool->GetDBIndex();
        m_password = pCachePool->GetPassword();
        m_pool_name = pCachePool->GetPoolName();
}
else
{
        log_error("pCachePool is NULL\n");
}

    m_pContext =NULL;
    m_last_connect_time =0;
}

CacheConn::~CacheConn()
{
if(m_pContext)
{
        redisFree(m_pContext);
        m_pContext =NULL;
}
}

/*
 * redis初始化连接和重连操作,类似mysql_ping()
 */
intCacheConn::Init()
{
if(m_pContext)// 非空,连接是正常的
{
return0;
}

// 1s 尝试重连一次
uint64_t cur_time =(uint64_t)time(NULL);
if(cur_time < m_last_connect_time +1)// 重连尝试 间隔1秒 
{
printf("cur_time:%lu, m_last_connect_time:%lu\n", cur_time, m_last_connect_time);
return1;
}
// printf("m_last_connect_time = cur_time\n");
    m_last_connect_time = cur_time;

// 1000ms超时
struct timeval timeout ={0,1000000};
// 建立连接后使用 redisContext 来保存连接状态。
// redisContext 在每次操作后会修改其中的 err 和  errstr 字段来表示发生的错误码(大于0)和对应的描述。
    m_pContext = redisConnectWithTimeout(m_server_ip.c_str(), m_server_port, timeout);

if(!m_pContext || m_pContext->err)
{
if(m_pContext)
{
            log_error("redisConnect failed: %s\n", m_pContext->errstr);
            redisFree(m_pContext);
            m_pContext =NULL;
}
else
{
            log_error("redisConnect failed\n");
}

return1;
}

    redisReply *reply;
// 验证
if(!m_password.empty())
{
        reply =(redisReply *)redisCommand(m_pContext,"AUTH %s", m_password.c_str());

if(!reply || reply->type == REDIS_REPLY_ERROR)
{
            log_error("Authentication failure:%p\n", reply);
if(reply)
                freeReplyObject(reply);
return-1;
}
else
{
// log_info("Authentication success\n");
}

        freeReplyObject(reply);
}

    reply =(redisReply *)redisCommand(m_pContext,"SELECT %d",0);

if(reply &&(reply->type == REDIS_REPLY_STATUS)&&(strncmp(reply->str,"OK",2)==0))
{
        freeReplyObject(reply);
return0;
}
else
{
if(reply)
            log_error("select cache db failed:%s\n", reply->str);
return2;
}
}

voidCacheConn::DeInit()
{
if(m_pContext)
{
        redisFree(m_pContext);
        m_pContext =NULL;
}
}

constchar*CacheConn::GetPoolName()
{
return m_pool_name.c_str();
}

stringCacheConn::get(string key)
{
string value;

if(Init())
{
return value;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"GET %s", key.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return value;
}

if(reply->type == REDIS_REPLY_STRING)
{
        value.append(reply->str, reply->len);
}

    freeReplyObject(reply);
return value;
}

stringCacheConn::set(string key, string &value)
{
string ret_value;

if(Init())
{
return ret_value;
}
// 返回的结果存放在redisReply
    redisReply *reply =(redisReply *)redisCommand(m_pContext,"SET %s %s", key.c_str(), value.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return ret_value;
}

    ret_value.append(reply->str, reply->len);
    freeReplyObject(reply);// 释放资源
return ret_value;
}

stringCacheConn::setex(string key, int timeout, string value)
{
string ret_value;

if(Init())
{
return ret_value;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"SETEX %s %d %s", key.c_str(), timeout, value.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return ret_value;
}

    ret_value.append(reply->str, reply->len);
    freeReplyObject(reply);
return ret_value;
}

boolCacheConn::mget(const vector<string> &keys, map<string, string> &ret_value)
{
if(Init())
{
returnfalse;
}
if(keys.empty())
{
returnfalse;
}

string strKey;
bool bFirst =true;
for(vector<string>::const_iterator it = keys.begin(); it != keys.end();++it)
{
if(bFirst)
{
            bFirst =false;
            strKey =*it;
}
else
{
            strKey +=" "+*it;
}
}

if(strKey.empty())
{
returnfalse;
}
    strKey ="MGET "+ strKey;
    redisReply *reply =(redisReply *)redisCommand(m_pContext, strKey.c_str());
if(!reply)
{
        log_info("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
returnfalse;
}
if(reply->type == REDIS_REPLY_ARRAY)
{
for(size_t i =0; i < reply->elements;++i)
{
            redisReply *child_reply = reply->element[i];
if(child_reply->type == REDIS_REPLY_STRING)
{
                ret_value[keys[i]]= child_reply->str;
}
}
}
    freeReplyObject(reply);
returntrue;
}

boolCacheConn::isExists(string &key)
{
if(Init())
{
returnfalse;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"EXISTS %s", key.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
returnfalse;
}
long ret_value = reply->integer;
    freeReplyObject(reply);
if(0== ret_value)
{
returnfalse;
}
else
{
returntrue;
}
}

longCacheConn::del(string &key)
{
if(Init())
{
return0;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"DEL %s", key.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return0;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::hdel(string key, string field)
{
if(Init())
{
return0;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"HDEL %s %s", key.c_str(), field.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return0;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

stringCacheConn::hget(string key, string field)
{
string ret_value;
if(Init())
{
return ret_value;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"HGET %s %s", key.c_str(), field.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return ret_value;
}

if(reply->type == REDIS_REPLY_STRING)
{
        ret_value.append(reply->str, reply->len);
}

    freeReplyObject(reply);
return ret_value;
}

boolCacheConn::hgetAll(string key, map<string, string> &ret_value)
{
if(Init())
{
returnfalse;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"HGETALL %s", key.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
returnfalse;
}

if((reply->type == REDIS_REPLY_ARRAY)&&(reply->elements %2==0))
{
for(size_t i =0; i < reply->elements; i +=2)
{
            redisReply *field_reply = reply->element[i];
            redisReply *value_reply = reply->element[i +1];

stringfield(field_reply->str, field_reply->len);
stringvalue(value_reply->str, value_reply->len);
            ret_value.insert(make_pair(field, value));
}
}

    freeReplyObject(reply);
returntrue;
}

longCacheConn::hset(string key, string field, string value)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"HSET %s %s %s", key.c_str(), field.c_str(), value.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::hincrBy(string key, string field, long value)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"HINCRBY %s %s %ld", key.c_str(), field.c_str(), value);
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::incrBy(string key, long value)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"INCRBY %s %ld", key.c_str(), value);
if(!reply)
{
        log_error("redis Command failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}
long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

stringCacheConn::hmset(string key, map<string, string> &hash)
{
string ret_value;

if(Init())
{
return ret_value;
}

int argc = hash.size()*2+2;
constchar**argv =newconstchar*[argc];
if(!argv)
{
return ret_value;
}

    argv[0]="HMSET";
    argv[1]= key.c_str();
int i =2;
for(map<string,string>::iterator it = hash.begin(); it != hash.end(); it++)
{
        argv[i++]= it->first.c_str();
        argv[i++]= it->second.c_str();
}

    redisReply *reply =(redisReply *)redisCommandArgv(m_pContext, argc, argv,NULL);
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
delete[] argv;

        redisFree(m_pContext);
        m_pContext =NULL;
return ret_value;
}

    ret_value.append(reply->str, reply->len);

delete[] argv;
    freeReplyObject(reply);
return ret_value;
}

boolCacheConn::hmget(string key, list<string> &fields, list<string> &ret_value)
{
if(Init())
{
returnfalse;
}

int argc = fields.size()+2;
constchar**argv =newconstchar*[argc];
if(!argv)
{
returnfalse;
}

    argv[0]="HMGET";
    argv[1]= key.c_str();
int i =2;
for(list<string>::iterator it = fields.begin(); it != fields.end(); it++)
{
        argv[i++]= it->c_str();
}

    redisReply *reply =(redisReply *)redisCommandArgv(m_pContext, argc,(constchar**)argv,NULL);
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
delete[] argv;

        redisFree(m_pContext);
        m_pContext =NULL;

returnfalse;
}

if(reply->type == REDIS_REPLY_ARRAY)
{
for(size_t i =0; i < reply->elements; i++)
{
            redisReply *value_reply = reply->element[i];
stringvalue(value_reply->str, value_reply->len);
            ret_value.push_back(value);
}
}

delete[] argv;
    freeReplyObject(reply);
returntrue;
}

longCacheConn::incr(string key)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"INCR %s", key.c_str());
if(!reply)
{
        log_error("redis Command failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}
long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::decr(string key)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"DECR %s", key.c_str());
if(!reply)
{
        log_error("redis Command failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}
long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::lpush(string key, string value)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"LPUSH %s %s", key.c_str(), value.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::rpush(string key, string value)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"RPUSH %s %s", key.c_str(), value.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

longCacheConn::llen(string key)
{
if(Init())
{
return-1;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"LLEN %s", key.c_str());
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
return-1;
}

long ret_value = reply->integer;
    freeReplyObject(reply);
return ret_value;
}

boolCacheConn::lrange(string key, long start, long end, list<string> &ret_value)
{
if(Init())
{
returnfalse;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"LRANGE %s %d %d", key.c_str(), start, end);
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
returnfalse;
}

if(reply->type == REDIS_REPLY_ARRAY)
{
for(size_t i =0; i < reply->elements; i++)
{
            redisReply *value_reply = reply->element[i];
stringvalue(value_reply->str, value_reply->len);
            ret_value.push_back(value);
}
}

    freeReplyObject(reply);
returntrue;
}

boolCacheConn::flushdb()
{
bool ret =false;
if(Init())
{
returnfalse;
}

    redisReply *reply =(redisReply *)redisCommand(m_pContext,"FLUSHDB");
if(!reply)
{
        log_error("redisCommand failed:%s\n", m_pContext->errstr);
        redisFree(m_pContext);
        m_pContext =NULL;
returnfalse;
}

if(reply->type == REDIS_REPLY_STRING &&strncmp(reply->str,"OK",2)==0)
{
        ret =true;
}

    freeReplyObject(reply);

return ret;
}
///
CachePool::CachePool(constchar*pool_name,constchar*server_ip,int server_port,int db_index,
constchar*password,int max_conn_cnt)
{
    m_pool_name = pool_name;
    m_server_ip = server_ip;
    m_server_port = server_port;
    m_db_index = db_index;
    m_password = password;
    m_max_conn_cnt = max_conn_cnt;
    m_cur_conn_cnt = MIN_CACHE_CONN_CNT;
}

CachePool::~CachePool()
{
    m_free_notify.Lock();
for(list<CacheConn*>::iterator it = m_free_list.begin(); it != m_free_list.end(); it++)
{
CacheConn*pConn =*it;
delete pConn;
}

    m_free_list.clear();
    m_cur_conn_cnt =0;
    m_free_notify.Unlock();
}

intCachePool::Init()
{
for(int i =0; i < m_cur_conn_cnt; i++)
{
CacheConn*pConn =newCacheConn(m_server_ip.c_str(), m_server_port,
                                         m_db_index, m_password.c_str(), m_pool_name.c_str());
if(pConn->Init())
{
delete pConn;
return1;
}

        m_free_list.push_back(pConn);
}

    log_info("cache pool: %s, list size: %lu\n", m_pool_name.c_str(), m_free_list.size());
return0;
}

CacheConn*CachePool::GetCacheConn()
{
    m_free_notify.Lock();

while(m_free_list.empty())
{
if(m_cur_conn_cnt >= m_max_conn_cnt)
{
            m_free_notify.Wait();
}
else
{
CacheConn*p_cache_conn =newCacheConn(m_server_ip.c_str(), m_server_port,
                                                    m_db_index, m_password.c_str(), m_pool_name.c_str());
int ret = p_cache_conn->Init();
if(ret)
{
                log_error("Init CacheConn failed\n");
delete p_cache_conn;
                m_free_notify.Unlock();
returnNULL;
}
else
{
                m_free_list.push_back(p_cache_conn);
                m_cur_conn_cnt++;
                log_info("new cache connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_cur_conn_cnt);
}
}
}

CacheConn*pConn = m_free_list.front();
    m_free_list.pop_front();

    m_free_notify.Unlock();

return pConn;
}

voidCachePool::RelCacheConn(CacheConn *p_cache_conn)
{
    m_free_notify.Lock();

list<CacheConn*>::iterator it = m_free_list.begin();
for(; it != m_free_list.end(); it++)
{
if(*it == p_cache_conn)
{
break;
}
}

if(it == m_free_list.end())
{
        m_free_list.push_back(p_cache_conn);
}

    m_free_notify.Signal();
    m_free_notify.Unlock();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.
  • 436.
  • 437.
  • 438.
  • 439.
  • 440.
  • 441.
  • 442.
  • 443.
  • 444.
  • 445.
  • 446.
  • 447.
  • 448.
  • 449.
  • 450.
  • 451.
  • 452.
  • 453.
  • 454.
  • 455.
  • 456.
  • 457.
  • 458.
  • 459.
  • 460.
  • 461.
  • 462.
  • 463.
  • 464.
  • 465.
  • 466.
  • 467.
  • 468.
  • 469.
  • 470.
  • 471.
  • 472.
  • 473.
  • 474.
  • 475.
  • 476.
  • 477.
  • 478.
  • 479.
  • 480.
  • 481.
  • 482.
  • 483.
  • 484.
  • 485.
  • 486.
  • 487.
  • 488.
  • 489.
  • 490.
  • 491.
  • 492.
  • 493.
  • 494.
  • 495.
  • 496.
  • 497.
  • 498.
  • 499.
  • 500.
  • 501.
  • 502.
  • 503.
  • 504.
  • 505.
  • 506.
  • 507.
  • 508.
  • 509.
  • 510.
  • 511.
  • 512.
  • 513.
  • 514.
  • 515.
  • 516.
  • 517.
  • 518.
  • 519.
  • 520.
  • 521.
  • 522.
  • 523.
  • 524.
  • 525.
  • 526.
  • 527.
  • 528.
  • 529.
  • 530.
  • 531.
  • 532.
  • 533.
  • 534.
  • 535.
  • 536.
  • 537.
  • 538.
  • 539.
  • 540.
  • 541.
  • 542.
  • 543.
  • 544.
  • 545.
  • 546.
  • 547.
  • 548.
  • 549.
  • 550.
  • 551.
  • 552.
  • 553.
  • 554.
  • 555.
  • 556.
  • 557.
  • 558.
  • 559.
  • 560.
  • 561.
  • 562.
  • 563.
  • 564.
  • 565.
  • 566.
  • 567.
  • 568.
  • 569.
  • 570.
  • 571.
  • 572.
  • 573.
  • 574.
  • 575.
  • 576.
  • 577.
  • 578.
  • 579.
  • 580.
  • 581.
  • 582.
  • 583.
  • 584.
  • 585.
  • 586.
  • 587.
  • 588.
  • 589.
  • 590.
  • 591.
  • 592.
  • 593.
  • 594.
  • 595.
  • 596.
  • 597.
  • 598.
  • 599.
  • 600.
  • 601.
  • 602.
  • 603.
  • 604.
  • 605.
  • 606.
  • 607.
  • 608.
  • 609.
  • 610.
  • 611.
  • 612.
  • 613.
  • 614.
  • 615.
  • 616.
  • 617.
  • 618.
  • 619.
  • 620.
  • 621.
  • 622.
  • 623.
  • 624.
  • 625.
  • 626.
  • 627.
  • 628.
  • 629.
  • 630.
  • 631.
  • 632.
  • 633.
  • 634.
  • 635.
  • 636.
  • 637.
  • 638.
  • 639.
  • 640.
  • 641.
  • 642.
  • 643.
  • 644.
  • 645.
  • 646.
  • 647.
  • 648.
  • 649.
  • 650.
  • 651.
  • 652.
  • 653.
  • 654.
  • 655.
  • 656.
  • 657.
  • 658.
  • 659.
  • 660.
  • 661.
  • 662.
  • 663.
  • 664.
  • 665.
  • 666.
  • 667.
  • 668.
  • 669.
  • 670.
  • 671.
  • 672.
  • 673.
  • 674.
  • 675.
  • 676.
  • 677.
  • 678.
  • 679.
  • 680.
  • 681.
  • 682.
  • 683.
  • 684.
  • 685.
  • 686.
  • 687.
  • 688.
  • 689.
  • 690.
  • 691.
  • 692.
  • 693.
  • 694.
  • 695.
  • 696.
  • 697.
  • 698.
  • 699.
  • 700.
  • 701.
  • 702.
  • 703.
  • 704.
  • 705.
  • 706.
  • 707.
  • 708.
  • 709.
  • 710.
  • 711.
  • 712.
  • 713.
  • 714.
  • 715.
  • 716.
  • 717.
  • 718.
  • 719.
  • 720.
  • 721.
  • 722.
  • 723.
  • 724.
  • 725.
  • 726.
  • 727.
  • 728.
  • 729.
  • 730.
  • 731.
  • 732.
  • 733.
  • 734.
  • 735.
  • 736.
  • 737.
  • 738.
  • 739.
  • 740.
  • 741.
  • 742.
  • 743.
  • 744.
  • 745.
  • 746.
  • 747.
  • 748.
  • 749.
  • 750.
  • 751.
  • 752.
  • 753.
  • 754.
  • 755.
  • 756.
  • 757.
  • 758.
  • 759.
  • 760.
  • 761.
  • 762.
  • 763.
  • 764.
  • 765.
  • 766.
  • 767.
  • 768.
  • 769.
  • 770.
  • 771.
  • 772.
  • 773.
  • 774.
  • 775.
  • 776.
  • 777.
  • 778.
  • 779.
  • 780.
  • 781.
  • 782.
  • 783.
  • 784.
  • 785.
  • 786.
  • 787.
  • 788.
  • 789.
  • 790.
  • 791.
  • 792.
  • 793.
  • 794.
  • 795.
  • 796.
  • 797.
  • 798.
  • 799.
  • 800.
  • 801.
  • 802.
  • 803.
  • 804.
  • 805.
  • 806.
  • 807.
  • 808.
  • 809.
  • 810.
  • 811.
  • 812.
  • 813.
  • 814.
  • 815.
  • 816.
  • 817.
  • 818.
  • 819.
  • 820.