目录
文章目录
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() 求每个参数的长度。
- argc 存放命令参数的个数,例如:
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 成员来查看错误信息。
相关阅读: