当前位置: 首页 > 工具软件 > hiRedis > 使用案例 >

C 语言编程 — hiredis 数据库客户端

戚阳
2023-12-01

目录

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

安装完成后 hiredis 的静态链接库和动态链接库都会存放到 /usr/local/lib/ 目录下:

$ ll /usr/local/lib/
总用量 716
-rw-r--r-- 1 root root 459182 6月  17 15:52 libhiredis.a
lrwxrwxrwx 1 root root     18 6月  17 15:52 libhiredis.so -> libhiredis.so.0.14
-rwxr-xr-x 1 root root 269776 6月  17 15:52 libhiredis.so.0.14

也可以将库文件拷贝到系统目录下:

$ 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\n", conn->errstr);
        } else {
            printf("Can't allocate redis context\n");
        }
    }

    redisReply *reply = redisCommand(conn, "set foo 123");
    freeReplyObject(reply);

    reply = redisCommand(conn, "get foo");
    printf("foo: %s\n", 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 参数是每个参数字符串的长度,函数会根据字符串长度来决定字符串的终止,而不是 ‘\0’。例如 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\n", 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 成员来查看错误信息。

 类似资料: