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


    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() */
    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;
    
    返回结果的类型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:返回值为整数 long long。
    ● 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 
    #include 
    #include 
    #include 
    #include "hiredis/hiredis.h"
    
    int main(int argc,char *argv[])
    {
        unsigned int j,isunix = 0;
        redisContext *c;
        redisReply *reply;
        const char *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);
        return 0;
    }
    
    • 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

    如果执行的时候报这个错误
    在这里插入图片描述
    解决办法:
    确保你的/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 
    
    static reactor_t *R;
    static int cnt, before, num;
    
    
    int current_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;
    }
    
    void getCallback(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);
        }
    }
    
    
    void connectCallback(const redisAsyncContext *c, int status) {
        if (status != REDIS_OK) {
            printf("Error: %s\n", c->errstr);
            stop_eventloop(R);
            return;
        }
    
        printf("Connected...\n");
    }
    
    void disconnectCallback(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);
    }
    
    int main(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);
            return 1;
        }
        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);
        return 0;
    }
    
    // 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
    • 84

    4、redis连接池实现

    1、Thread.h头文件

    #ifndef __THREAD_H__
    #define __THREAD_H__
    #include 
    #include 
    
    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);
        }
        void Lock()
        {
            pthread_mutex_lock(&m_mutex);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&m_mutex);
        }
        void Wait()
        {
            pthread_cond_wait(&m_cond, &m_mutex);
        }
        // 返回0则正常,其他值为异常
        int WaitTime(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);
        }
        void Signal()
        {
            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
    • 68

    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 
    #include 
    #include 
    #include 
    
    #include "Thread.h"
    
    #include "hiredis.h"
    
    
    using std::string;
    using std::list;
    using std::map; 
    using std::vector; 
    
    class CachePool;
    
    class CacheConn {
    public:
    	CacheConn(const char* server_ip, int server_port, int db_index, const char* password, 
    		const char *pool_name ="");
    	CacheConn(CachePool* pCachePool);	
    	virtual ~CacheConn();
    	
    	int Init();
    	void DeInit();
    	const char* GetPoolName();
        // 通用操作
        // 判断一个key是否存在
        bool isExists(string &key);
        // 删除某个key
        long del(string &key);
    
        // ------------------- 字符串相关 -------------------
    	string get(string key);
        string set(string key, string& value);
    	string setex(string key, int timeout, string value);
    	
    	// string mset(string key, map);
        //批量获取
        bool mget(const vector<string>& keys, map<string, string>& ret_value);
    	//原子加减1
        long incr(string key);
        long decr(string key);
    
    
    	// ---------------- 哈希相关 ------------------------
    	long hdel(string key, string field);
    	string hget(string key, string field);
    	bool hgetAll(string key, map<string, string>& ret_value);
    	long hset(string key, string field, string value);
    
    	long hincrBy(string key, string field, long value);
        long incrBy(string key, long value);
    	string hmset(string key, map<string, string>& hash);
    	bool hmget(string key, list<string>& fields, list<string>& ret_value);
        
        
    
    	// ------------ 链表相关 ------------
    	long lpush(string key, string value);
    	long rpush(string key, string value);
    	long llen(string key);
    	bool lrange(string key, long start, long end, list<string>& ret_value);
    
    	
        bool flushdb();
    
    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(const char* pool_name, const char* server_ip, int server_port, int db_index, 
    		const char *password, int max_conn_cnt);
    	virtual ~CachePool();
    
    	int Init();
        // 获取空闲的连接资源
    	CacheConn* GetCacheConn();
        // Pool回收连接资源
    	void RelCacheConn(CacheConn* pCacheConn);
    
    	const char* GetPoolName() { return m_pool_name.c_str(); }
    	const char* GetServerIP() { return m_server_ip.c_str(); }
    	const char* GetPassword() { return m_password.c_str(); }
    	int GetServerPort() { return m_server_port; }
    	int GetDBIndex() { 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
    • 126

    3、CachePool.cpp文件

    #include "CachePool.h"
    
    #include 
    #include 
    #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(const char *server_ip, int server_port, int db_index, const char *password,
    					 const char *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()
     */
    int CacheConn::Init()
    {
    	if (m_pContext)	// 非空,连接是正常的
    	{
    		return 0;
    	}
    
    	// 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);
    		return 1;
    	}
    	// 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");
    		}
    
    		return 1;
    	}
    
    	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);
    		return 0;
    	}
    	else
    	{
    		if (reply)
    			log_error("select cache db failed:%s\n", reply->str);
    		return 2;
    	}
    }
    
    void CacheConn::DeInit()
    {
    	if (m_pContext)
    	{
    		redisFree(m_pContext);
    		m_pContext = NULL;
    	}
    }
    
    const char *CacheConn::GetPoolName()
    {
    	return m_pool_name.c_str();
    }
    
    string CacheConn::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;
    }
    
    string CacheConn::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;
    }
    
    string CacheConn::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;
    }
    
    bool CacheConn::mget(const vector<string> &keys, map<string, string> &ret_value)
    {
    	if (Init())
    	{
    		return false;
    	}
    	if (keys.empty())
    	{
    		return false;
    	}
    
    	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())
    	{
    		return false;
    	}
    	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;
    		return false;
    	}
    	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);
    	return true;
    }
    
    bool CacheConn::isExists(string &key)
    {
    	if (Init())
    	{
    		return false;
    	}
    
    	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;
    		return false;
    	}
    	long ret_value = reply->integer;
    	freeReplyObject(reply);
    	if (0 == ret_value)
    	{
    		return false;
    	}
    	else
    	{
    		return true;
    	}
    }
    
    long CacheConn::del(string &key)
    {
    	if (Init())
    	{
    		return 0;
    	}
    
    	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;
    		return 0;
    	}
    
    	long ret_value = reply->integer;
    	freeReplyObject(reply);
    	return ret_value;
    }
    
    long CacheConn::hdel(string key, string field)
    {
    	if (Init())
    	{
    		return 0;
    	}
    
    	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;
    		return 0;
    	}
    
    	long ret_value = reply->integer;
    	freeReplyObject(reply);
    	return ret_value;
    }
    
    string CacheConn::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;
    }
    
    bool CacheConn::hgetAll(string key, map<string, string> &ret_value)
    {
    	if (Init())
    	{
    		return false;
    	}
    
    	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;
    		return false;
    	}
    
    	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];
    
    			string field(field_reply->str, field_reply->len);
    			string value(value_reply->str, value_reply->len);
    			ret_value.insert(make_pair(field, value));
    		}
    	}
    
    	freeReplyObject(reply);
    	return true;
    }
    
    long CacheConn::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;
    }
    
    long CacheConn::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;
    }
    
    long CacheConn::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;
    }
    
    string CacheConn::hmset(string key, map<string, string> &hash)
    {
    	string ret_value;
    
    	if (Init())
    	{
    		return ret_value;
    	}
    
    	int argc = hash.size() * 2 + 2;
    	const char **argv = new const char *[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;
    }
    
    bool CacheConn::hmget(string key, list<string> &fields, list<string> &ret_value)
    {
    	if (Init())
    	{
    		return false;
    	}
    
    	int argc = fields.size() + 2;
    	const char **argv = new const char *[argc];
    	if (!argv)
    	{
    		return false;
    	}
    
    	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, (const char **)argv, NULL);
    	if (!reply)
    	{
    		log_error("redisCommand failed:%s\n", m_pContext->errstr);
    		delete[] argv;
    
    		redisFree(m_pContext);
    		m_pContext = NULL;
    
    		return false;
    	}
    
    	if (reply->type == REDIS_REPLY_ARRAY)
    	{
    		for (size_t i = 0; i < reply->elements; i++)
    		{
    			redisReply *value_reply = reply->element[i];
    			string value(value_reply->str, value_reply->len);
    			ret_value.push_back(value);
    		}
    	}
    
    	delete[] argv;
    	freeReplyObject(reply);
    	return true;
    }
    
    long CacheConn::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;
    }
    
    long CacheConn::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;
    }
    
    long CacheConn::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;
    }
    
    long CacheConn::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;
    }
    
    long CacheConn::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;
    }
    
    bool CacheConn::lrange(string key, long start, long end, list<string> &ret_value)
    {
    	if (Init())
    	{
    		return false;
    	}
    
    	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;
    		return false;
    	}
    
    	if (reply->type == REDIS_REPLY_ARRAY)
    	{
    		for (size_t i = 0; i < reply->elements; i++)
    		{
    			redisReply *value_reply = reply->element[i];
    			string value(value_reply->str, value_reply->len);
    			ret_value.push_back(value);
    		}
    	}
    
    	freeReplyObject(reply);
    	return true;
    }
    
    bool CacheConn::flushdb()
    {
    	bool ret = false;
    	if (Init())
    	{
    		return false;
    	}
    
    	redisReply *reply = (redisReply *)redisCommand(m_pContext, "FLUSHDB");
    	if (!reply)
    	{
    		log_error("redisCommand failed:%s\n", m_pContext->errstr);
    		redisFree(m_pContext);
    		m_pContext = NULL;
    		return false;
    	}
    
    	if (reply->type == REDIS_REPLY_STRING && strncmp(reply->str, "OK", 2) == 0)
    	{
    		ret = true;
    	}
    
    	freeReplyObject(reply);
    
    	return ret;
    }
    ///
    CachePool::CachePool(const char *pool_name, const char *server_ip, int server_port, int db_index,
    					 const char *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();
    }
    
    int CachePool::Init()
    {
    	for (int i = 0; i < m_cur_conn_cnt; i++)
    	{
    		CacheConn *pConn = new CacheConn(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;
    			return 1;
    		}
    
    		m_free_list.push_back(pConn);
    	}
    
    	log_info("cache pool: %s, list size: %lu\n", m_pool_name.c_str(), m_free_list.size());
    	return 0;
    }
    
    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 = new CacheConn(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();
    				return NULL;
    			}
    			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;
    }
    
    void CachePool::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
    • 821
  • 相关阅读:
    乘性散斑噪声理论及其仿真实例分析
    【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例实践应用
    数仓工具—Hive实战之生成连续序列(23)
    Kakao账号如何注册使用?如何Kakao多开?外贸必备全面教程
    将字体颜色设置为渐变色 --字体倾斜--数组转字符串--旋转(一些样式的设置)
    SpringBoot 整合 Shiro 权限框架
    [python][学习]循环与嵌套---打印乘法口诀
    敞开心扉地说家人们,爬也要爬进互联网大厂
    ThreadPoolExecutor 线程池参数详解,执行流程
    Spring Cloud Alibaba整合Seata实战
  • 原文地址:https://blog.csdn.net/weixin_45715405/article/details/133705328