• hiredis — Redis 的 C 语言客户端


    目录

    Redis 客户端

    Redis 拥有几乎所有主流编程语言的客户端(https://redis.io/clients),其中 C 语言的客户端推荐使用:

    • hiredis:https://github.com/redis/hiredis
    • hiredis-vip:https://github.com/vipshop/hiredis-vip
    • hiredispool:https://github.com/aclisp/hiredispool

    HIREDIS

    hiredis 是一个轻量级的 C 语言编程客户端。它非常的简洁,仅仅提供了对 redis 通信协议的最小支持。同时它使用了一个高级别的 printf-like API,所以对于习惯了 printf 风格的 C 编程用户来说,其非常容易使用,而且 API 中没有明确的绑定每个 Redis 命令。

    安装

    $ git clone https://github.com/redis/hiredis.git
    $ cd hiredis
    $ make 
    $ make install
    $ cp libhiredis.so /usr/lib64 /usr/lib 
    $ /sbin/ldconfig

    验证

    #include <stdio.h>
    #include <hiredis/hiredis.h>
    
    
    int main() {
        redisContext *conn = redisConnect("127.0.0.1", 6379);
        if (conn == NULL || conn->err) {
            if (conn) {
                printf("Error: %s
    ", conn->errstr);
            } else {
                printf("Can't allocate redis context
    ");
            }
        }
    
        redisReply *reply = redisCommand(conn, "set foo 123");
        freeReplyObject(reply);
    
        reply = redisCommand(conn, "get foo");
        printf("foo: %s
    ", reply->str);
        freeReplyObject(reply);
    
        redisFree(conn);
        return 0;
    }
    

    编译运行:

    $ gcc test.c -o test -lhiredis
    $  ./test
    foo: 123

    Synchronous API

    使用 hiredis 的 Synchronous API,需要掌握以下函数原型。

    redisConnect

    redisContext*redisConnect(const char *ip, int port)
    

    指针函数,输入 Redis Server 的 IP:Port,返回 redisContext 结构体类型指针。

    redisContext 结构体用于保存连接状态,包含了一个 Int 类型的结构体成员 err。当 err 非空时,表示连接处于一个错误状态,此时可以通过查看 String 类型的 errstr 成员来获取错误信息。所以在调用 redisConnect() 后应该检查 redisContext 结构体的 err 成员来判断连接是否成功。

    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;
    

    NOTE:redisContext 是线程非安全的,也就是说,多个线程同时访问一个 redisContext 很可能会出现问题。

    redisCommand

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

    空类型指针函数,具有可变长形参,输入 redisContext 结构体类型指针、Redis SQL 语句字符串。返回值为空类型指针,可以强制类型转换为任意类型,一般强制转换成为 redisReply 类型,然后对其进行后续处理,例如:查看执行结果或执行错误的结果。

    redisCommand() 是一个 printf-like API,向 Redis Server 发送指令。

    • 最简单的形式:
    reply= redisCommand(context, "SET foo bar");
    
    • 使用 %s 插入字符串的形式:
    reply= redisCommand(context, "SET foo %s", value);
    
    • 发送多个分离的字符串的形式:
    reply= redisCommand(context, "SET %s %s", key, value);
    
    • 发送二进制字符串的形式,需要指出字符串的长度:
    reply= redisCommand(context, "SET foo %b", (size_t)valuelen);
    
    • 类似于命令行的形式:
      • argc 存放命令参数的个数,例如:argc = 3
      • argv 存放每个命令参数的指针,例如:argv = {"set", "foo", "bar"}
      • argvlen 参数是每个参数字符串的长度,函数会根据字符串长度来决定字符串的终止,而不是 ‘’。例如 set foo bar 的 argvlen == {3, 3, 3}。如果 argvlen 为空,那么函数内部会自动调用 strlen() 求每个参数的长度。
    void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
    
    // EXAMPLE:
    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);
    

    当 redisCommand() 调用失败时,返回为 NULL,同时设置 redisContext 结构体的 err 成员。一旦错误返回,redisContext 不能被重用,此时应该建立一个新的连接。

    当 redisCommand() 成功调用时,返回的 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;
    

    redisReply 结构体的 type 成员应该用于检查 Reply 类型,包括:

    • REDIS_REPLY_STRING == 1:字符串回复。返回值是字符串,字符串数值为 redis->str,字符串长度为 redis->len
    • REDIS_REPLY_ARRAY == 2:批量操作回复。返回值是数组,数组大小为 redis->elements,数组值元素为 redis->element[i],每个元素都是一个 type==REDIS_REPLY_STRING 的 redisReply 结构体类型指针。元素的返回值可以通过 redis->element[i]->str 访问。
    • REDIS_REPLY_INTEGER == 3:整型答复。返回值为 long long int,数值为 reply->integer
    • REDIS_REPLY_NIL == 4:零(nil)对象回复。表示执行结果为空,没有可以访问的数据。
    • REDIS_REPLY_STATUS == 5:状态回复。返回命令执行的状态,状态信息为 reply->str,状态字符串长度为 reply->len。比如:set foo bar 返回的状态为 OK,则 reply->str 为 “OK”。
    • REDIS_REPLY_ERROR == 6 :错误回复。命令执行错误,错误信息存放在 reply->str 当中。

    freeReplyObject

    void freeReplyObject(void *reply);
    

    空类型函数,输入 redisCommand() 返回的 redisReply 指针变量,并释放 redisReply 指针变量所占用的内存。

    在代码中应该使用 freeReplyObject() 来释放 Reply 占用的内存。注意,freeReplyObject() 会递归的释放数组中的资源,不需要手动释放数组资源。

    redisFree

    void redisFree(redisContext *c);
    

    空类型函数,输入 redisConnect() 返回的 conn 指针变量。该 API 会断开 Redis Server 的连接(关闭 Socket)并释放 redisContext 结构体所占用的内存。

    Asynchronous API

    同步调用简单,而异步调用的性能更高。hiredis 的 Asynchronous API 可以简易的与一些基于事件(Event)的函数库结合使用,例如 libev 和 ibevent。

    redisAsyncConnect

    redisAsyncConnect() 用于与 Redis Server 建立非阻塞连接,返回 redisAsyncContext 结构体类型指针,结构体的 err 成员用来检查连接的过程中是否发生了错误。注意,因为创建的是非阻塞的连接,所以 Kernel 并不会立马返回连接的结果。

    redisAsyncContext*c = redisAsyncConnect("127.0.0.1", 6379);
    if (c->err) {
        printf("Error: %s
    ", c->errstr);
        // handle error
    }
    

    同样,redisAsyncContext 也是线程非安全的。

    Sending commands and their callbacks

    在异步调用场景中,Redis 的操作命令被加入到事件循环队列,当命令执行完后再调用相应的回调函数来获得执行结果。回调函数的原型为:

    void(redisAsyncContext *c, void *reply, void *privdata);
    
    • privdata:是由调用者自己定义的数据类型。

    以下是进行异步命令操作的函数:

    int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
    int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
    

    与同步接口调用类似。执行成功返回 REDIS_OK,否则返回 REDIS_ERR。比如:在连接已经关闭的情况下再调用 redisAsyncCommand() 就会返回 REDIS_ERR。回调执行完毕后如果 reply 不为空,那么回调执行完毕后将自动对 reply 的资源进行回收。而当 Context 发生错误时,回调得到的 reply 则为空。

    redisAsyncDisconnect

    一个异步的连接可以通过下面这个函数终止:

    void redisAsyncDisconnect(redisAsyncContext *ac);
    

    当这个函数被调用时,异步连接并不会被立即关闭,而是等待所有与这个连接关联的异步命令操作执行完毕,并且回调事件已经执行完毕后才关闭此连接,这时在响应关闭连接事件的回调函数中得到的状态为 REDIS_OK,此连接的资源也将会被自动回收。

    异步的 Context 可设置一个响应断开连接事件的回调函数,当连接断开时会相应执行。回调函数的原型为:

    void(const redisAsyncContext *c, int status);
    

    在断开连接的情况下,当连接是由用户自己断开的,那么 status 参数为 REDIS_OK。如果出现了其他错误,则 status 参数为 REDIS_ERR,此时可以通过 err 成员得到准确的错误码。

    一个异步 Context 仅能设置一次断开连接的回调,如果再进行下一次设置将会返回 REDIS_ERR。设置断开连接回调函数的原型为:

    int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
    

    当回调执行完毕后 Context 会自己释放资源。该回调事件给创建一个新连接提供了便利。

    Pipelining

    当 redisCommand 系列的 API 被调用时,hiredis 首先会根据 Redis 协议格式化该 API 传入的命令。然后将格式化的命令放入 redisContext 的输出缓冲区(output buffer)中。输出缓冲区是动态的,所以它可以容纳任意数量的命令。将命令放入输出缓冲区后,可以调用redisGetReply() 来获取执行命令的结果。redisGetReply() 有以下两种执行场景:

    • 输入缓冲区(input buffer)为空:

      • 尝试解析来自输入缓冲区的单个回复(Reply)并将其返回。
      • 如果没有回复可以被解析,则进入第 2 种场景。
    • 输入缓冲区不为空:

      • 将输出缓冲区的内容写入套接字,传输到 Redis Server。
      • 轮询从套接字读取,直到可以解析单个回复。

    对于序列化的命令,唯一需要做的事情就是将其填充到输出缓冲区。由于这个原因,hiredis 还可以使用 redisAppendCommand 来发送命令,并使用 redisGetReply 获取命令的单个回复。

    void redisAppendCommand(redisContext *c, const char *format, ...);
    void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
    

    redisGetReply() 返回值为 REDIS_OK 或 REDIS_ERR,是一个 int 类型。后者表示读取回复时出错,同样可以通过 err 成员获取到错误信息。

    hiredis Pipelining 的使用方法如下:

    redisReply*reply;
    redisAppendCommand(context,"SET foo bar");
    redisAppendCommand(context,"GET foo");
    redisGetReply(context,(void *)&reply); // reply for SET
    freeReplyObject(reply);
    redisGetReply(context,(void *)&reply); // reply for GET
    freeReplyObject(reply);
    

    该 APIs 还可以实现 Redis 的发布订阅机制(blocking subscriber):

    reply= redisCommand(context, "SUBSCRIBE foo");
    freeReplyObject(reply);
    while(redisGetReply(context, (void *)&reply) == REDIS_OK) {
        // consume message
        freeReplyObject(reply);
    }
    

    Errors

    当 hiredis 提供的 API(函数)调用不成功时,会返回 NULL 或 REDIS_ERR。此时 redisContext 结构体的 err 成员为非 0,并设置为以下常量之一:

    • REDIS_ERR_IO:创建连接时发生了 I/O 错误(尝试写入套接字或从套接字读取)。此时可以在程序中 include errno.h,并使用使用全局变量 errno 来查询错误。
    • REDIS_ERR_EOF:Redis Server 已经关闭导致的错误。
    • REDIS_ERR_PROTOCOL:解析 redis 协议时发生错误。
    • REDIS_ERR_OTHER:任何其他的错误。

    以上错误,都可以通过 redisContext 结构体的 errstr 成员来查看错误信息。

    相关阅读:

  • 相关阅读:
    低功耗计算机视觉技术前沿,四大方向,追求更小、更快、更高效
    ELECTRA中文预训练模型开源,性能依旧媲美BERT
    局部敏感哈希源代码-python
    利用局部敏感哈希改进推荐系统实时性
    局部敏感哈希算法介绍
    为什么要用局部敏感哈希
    多采用panda的数据处理方式
    delphi:文件操作
    delphi:常用函数
    delphi:打印日志
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309249.html
Copyright © 2020-2023  润新知