1.简单介绍
hiredis是一个轻量级的访问redis数据库的c客户端。
它是轻量级的不仅仅是因为它仅仅提供对协议的最小支持,而且它使用了一个高级别的极度类似于printf的api使它的级别远高于其最小代码库和缺乏绑定的redis命令。简而言之,就是更灵活。
除了支持发送命令和接受命令,它还有一个与io层分离的回复解析器。它是一个简单灵活的流解析器,可以用于更高级别的语言绑定以实现有效的回复解析。
hiredis仅仅支持二进制安全的redsi协议,因此你可以使用它在redis的版本的大于1.2.0.
hiredis提供多套api,包括同步的api,异步api,回复解析的api.
2.同步api
要使用同步api,仅仅需要学会使用几个函数。
redisContext *redisConnect(const char *ip, int port);
void redisFree(redisContext *c);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);
1)redisConnect返回一个redisContext的结构体, 这个结构用于保存连接状态。这个结构体包含一个integer类型的err数据成员,这个成员非0,当连接处于一个错误状态的时候,另一个string的数据成员指出具体的错误原因。在调用redisConnect之后应该检查err以测试是否连接成功。
2)向redis发送命令有好几种方式。
第一种方式:
redisCommand,提供了一种类似于printf的形式去发送命令。第一个参数是redisConnect的返回值。
最简单的形式:
reply = redisConnect(context, "set foo bar");
使用%s插入字符串的形式:
reply = redisConnect(context, "SET foo %s", value);
发送二进制字符串的形式,但同时需要指出字符串的长度:
reply = redisCommand(context, "SET foo %b", (szie_t)valuelen);
发送多个分离的字符串的形式:
reply = redisCommand(context, "set key:%s %s", myid, value);
类似于命令行的形式:
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
第四个参数是每个参数字符串的长度
回复:
当命令成功执行时,redisCommand的返回值会保留一个回复。发生错误时,返回值为NULL,上下文中的err字段将被设置(请参阅错误部分)。一旦错误返回,上下文不能被重用,你应该建立一个新的连接。
标准回复redisCommand的类型是redisReply。 redisReply中的类型字段应该用于测试收到的回复类型:
REDIS_REPLY_STATUS:
该命令回复了状态回复。状态字符串可以使用reply-> str来访问。该字符串的长度可以使用reply-> len来访问。
REDIS_REPLY_ERROR:
该命令回复了一个错误。错误字符串可以与REDIS_REPLY_STATUS相同访问。
REDIS_REPLY_INTEGER:
该命令用一个整数来回答。整数值可以使用long long类型的reply-> integer字段来访问。
REDIS_REPLY_NIL:
该命令回答了一个零对象。没有要访问的数据。
REDIS_REPLY_STRING:
批量(字符串)回复。答复的值可以使用reply-> str来访问。该字符串的长度可以使用reply-> len来访问。
REDIS_REPLY_ARRAY:
多批量回复。多批量答复中的元素数量存储在reply->元素中。多批量回复中的每个元素也是一个redisReply对象,可以通过reply-> element [.. index ..]进行访问。 Redis可能会回应嵌套数组,但这完全受支持。
应使用freeReplyObject()函数释放回复。 请注意,这个函数将负责释放包含在数组和嵌套数组中的子回复对象,所以用户不需要释放子回复(它实际上是有害的并且会损坏内存)。
3)将命令序列化
当redisCommand系列中的任何函数被调用时,Hiredis首先根据Redis协议格式化该命令。然后将格式化的命令放入redisContext的输出缓冲区中。这个输出缓冲区是动态的,所以它可以容纳任意数量的命令。将命令放入输出缓冲区后,调用redisGetReply。这个函数有以下两个执行路径:
输入缓冲区非空:
尝试解析来自输入缓冲区的单个回复并将其返回
如果没有答复可以解析,相当于输入缓冲区为空的情况
输入缓冲区为空:
将整个输出缓冲区写入套接字
从套接字读取,直到可以解析单个回复
对于序列化命令,唯一需要做的事情是填充输出缓冲区。由于这个原因,除了不返回一个回复之外,可以使用两个与redisCommand系列相同的命令,然后我们用redisGetReply获取命令的返回结果。
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
官方文档如上描述,但其实源码中这两个命令的返回值是int。
使用方法如下:
redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);
4)error
当函数调用不成功时,根据函数返回NULL或REDIS_ERR。 context中的err字段将为非零值,并设置为以下常量之一:
REDIS_ERR_IO:创建连接时发生I / O错误,尝试写入套接字或从套接字读取。 如果您在应用程序中包含errno.h,则可以使用全局errno变量来找出错误。
REDIS_ERR_EOF:服务器关闭导致空读的连接。
REDIS_ERR_PROTOCOL:解析协议时发生错误。
REDIS_ERR_OTHER:任何其他错误。 目前,只有在指定的主机名连接时才能使用它。
在任何情况下,context中的errstr字段都将设置为保存错误的字符串表示形式
3.代码
编译的时候切记加上动态链接库。可以使用pkg-config --cflags --libs hiredis显示如何加。
#include <stdio.h>
#include <stdlib.h>
#include <hiredis.h>
void prase(redisReply *reply)
{
int seq = 0;
if (NULL == reply)
{
printf("redisReply id NULL\n");
return;
}
switch (reply->type)
{
case REDIS_REPLY_STATUS:
printf("REDIS_REPLY_STATUS:%s\n", reply->str);
break;
case REDIS_REPLY_ERROR:
printf("REDIS_REPLY_ERROR:%s\n", reply->str);
break;
case REDIS_REPLY_INTEGER:
printf("REDIS_REPLY_INTEGER:%lld\n", reply->integer);
break;
case REDIS_REPLY_NIL:
printf("REDIS_REPLY_NIL:NULL\n");
break;
case REDIS_REPLY_STRING:
printf("REDIS_REPLY_STRING:%s\n", reply->str);
break;
case REDIS_REPLY_ARRAY:
printf("REDIS_REPLY_ARRAY\n");
while (seq < reply->elements)
{
printf(" REDIS_REPLY_STRING:%s ", reply->element[seq]->str);
if (seq++ % 3 == 0)
{
printf("\n");
}
}
printf("\n");
break;
default:
break;
}
freeReplyObject(reply);
}
int main()
{
redisContext *c = redisConnect("127.0.0.1", 6379);
if ((c == NULL) || c->err)
{
if (c)
{
printf("Error:%s\n", c->errstr);
}
else
{
printf("conn't allocate redis context\n");
}
exit(-1);
}
printf("redis connect return %d:%s\n", c->err, c->errstr);
redisReply *reply = (redisReply*)redisCommand(c, "set company noahwm");
prase(reply);
reply = (redisReply*)redisCommand(c, "set phone 17621079235");
prase(reply);
reply = (redisReply*)redisCommand(c, "keys *");
prase(reply);
return 0;
}
修改main函数
int main()
{
redisContext *c = redisConnect("127.0.0.1", 6379);
if ((c == NULL) || c->err)
{
if (c)
{
printf("Error:%s\n", c->errstr);
}
else
{
printf("conn't allocate redis context\n");
}
exit(-1);
}
printf("redis connect success\n");
redisReply *reply = (redisReply*)redisCommand(c, "set company noahwm");
prase(reply);
reply = (redisReply*)redisCommand(c, "set phone 17621079235");
prase(reply);
redisAppendCommand(c, "set family henan");
redisGetReply(c, (void**)&reply); // reply for SET
prase(reply);
reply = (redisReply*)redisCommand(c, "keys *");
prase(reply);
return 0;
}
如果使用完append类的command函数之后我们不使用redisGetReply得到回复,那么下一个命令的回复就是上一条命令的回复
密码验证
int main()
{
c = redisConnect((char*)redis_host, redis_port);
if (c->err) /* Error flags, 0 when there is no error */
{
printf("连接Redis失败: %s\n", c->errstr);
exit(1);
}
else
{
printf("连接Redis成功!\n");
}
reply = (redisReply *)redisCommand(c, "AUTH %s", redis_password);
if (reply->type == REDIS_REPLY_ERROR)
{
printf("Redis认证失败!\n");
}
else
{
printf("Redis认证成功!\n");
}
freeReplyObject(reply);
}