hiredis(Synchronous API)
hiredis是一个小型的client端的c库。它只增加了最小对协议的支持,同时它用一个高级别的printf-alike API为了绑定各种redis命令。除了支持发送和接收命令,它还支持对流的解析。hiredis仅支持binary-safe的redis协议,所以需要用的redis版本>=1.2.0. 这个库包括多个API, 包括同步API,异步API和返回的解析API等。
安装hiredis:
1)编译完redis后,在/users/denver/rsun/test/redis/redis-2.6.10/deps/hiredis目录下会生成相应的动态链接库libhiredis.so和静态链接库libhiredis.a,
2)执行make install
会将头文件和库文件copy到指定的目录中:
dcmvrh12% make install
mkdir -p /u1/rsun/test/redis/redis-2.6.10/include/hiredis /u1/rsun/test/redis/redis-2.6.10/lib
cp -a hiredis.h async.h adapters /u1/rsun/test/redis/redis-2.6.10/include/hiredis
cp -a libhiredis.so /u1/rsun/test/redis/redis-2.6.10/lib/libhiredis.so.0.10
cd /u1/rsun/test/redis/redis-2.6.10/lib && ln -sf libhiredis.so.0.10 libhiredis.so.0
cd /u1/rsun/test/redis/redis-2.6.10/lib && ln -sf libhiredis.so.0 libhiredis.so
cp -a libhiredis.a /u1/rsun/test/redis/redis-2.6.10/lib
安装完就可以使用了。
关于hiredis支持的Synchronous API,主要包括以下函数:
redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);
1 连接
redisConnect函数用来生成redisContext。该上下文用来存储connect状态。redisContext结构有一个整形err字段(非0值)用来保存连接的错误状态。字段errstr用来保存错误描述。当通过redisConnect连接redis结束后,可以check err字段来查看连接是否成功。
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c != NULL && c->err) {
printf("Error: %s\n", c->errstr);
// handle error
}
1) redisContext结构体存储中存储错误信息,文件描述符fd,写缓冲区,redisReader类对象指针。
typedef struct redisContext {
int err;
char errstr[128];
int fd;
int flags;
char *obuf;
redisReader *reader;
} redisContext;
2) redisConnect函数实现连接到一个redis server上。
redisContext *redisConnect(const char *ip, int port) {
redisContext *c = redisContextInit();
c->flags |= REDIS_BLOCK; //设置连接类型是阻塞的
redisContextConnectTcp(c,ip,port,NULL);
return c;
}
- redisContextInit函数用于初始化redisContext结构体指针。
- redisContextConnectTcp函数的定义位于net.c中
根据输入的ip和port绑定地址,使用TCP连接,通过getaddrinfo函数;
创建socket;
设置socket属性信息(阻塞),通过fcntl函数;
连接redis server端,通过connect函数;
设置socket属性信息,通过setsockopt函数;
2 发送命令
有许多方式来发送命令给redis。redisCommand方法和printf相似:
reply = redisCommand(context, "SET foo bar");
可以用%s来表示string类型。
reply = redisCommand(context, "SET foo %s", value);
当需要发送二进制安全的string类型时,需要使用%b,后面跟string类型以及他的长度。
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
redisCommand函数实现:
void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap;
void *reply = NULL;
va_start(ap,format);
reply = redisvCommand(c,format,ap);
va_end(ap);
return reply;
}
1) 采用不定参数的函数实现
2) 调用redisvCommand函数来解析
- redisvFormatCommand(char **target, const char *format, va_list ap)
解析format字符串,根据format的内容从ap中取相应的数据;
- __redisAppendCommand(redisContext *c, char *cmd, size_t len)将格式化的命令写到输出缓冲区中。
3 返回值
通过redisCommand函数的返回值来查看是否执行成功。当有错误发生时,返回值为NULL,同时err字段被标示。一旦context返回错误,则该context不能被重用,需要新建一个新的connect。
REDIS_REPLY_STATUS
: 返回状态,状态字符串被放在reply->str中,状态字符串长度放在reply->len中。
REDIS_REPLY_ERROR
: 返回错误
REDIS_REPLY_INTEGER
: 返回整型值,该值被reply->integer接收,类型为long long
REDIS_REPLY_NIL
: 返回一个nil对象,没有数据被接收。
REDIS_REPLY_STRING
: 返回一个字符串,字符串被放在reply->str中,字符串长度放在reply->len中。
REDIS_REPLY_ARRAY
: 多个元素被返回,元素的数量存放在reply->elements中。每个元素的返回值存放在redisReply中。
返回值需要被释放通过freeReplyObject()函数。
void freeReplyObject(void
*reply)根据reply->type的不同值执行不同的free操作。
4
清空
断开及清空context,使用下列函数:
void redisFree(redisContext *c);
该函数关闭socket,同时做释放操作。
根据不同对象,执行不同的free操作。
void redisFree(redisContext *c) {
if (c->fd > 0)
close(c->fd);
if (c->obuf != NULL)
sdsfree(c->obuf);
if (c->reader != NULL)
redisReaderFree(c->reader);
free(c);
}
5 发送命令
redisCommandArgv函数用来处理多个命令。
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
argc中存放命令数量;argv数组中存放string数组,argvlen为数组长度。
该函数返回值和redisCommand相同。
函数定义如下:
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK)
return NULL;
return __redisBlockForReply(c);
}
- int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen)
调用redisFormatCommandArgv函数解析输入命令集合
- __redisBlockForReply(redisContext *c)命令集合放到缓冲区中
e.g.:
#include < stdio.h >
#include < stdlib.h >
#include < string.h >
#include "hiredis.h"
int main(void) {
unsigned int j;
redisContext *c;
redisReply *reply;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout((char*)"127.0.0.2", 6379, timeout);
if (c->err) {
printf("Connection error: %s\n", c->errstr);
exit(1);
}
reply = redisCommand(c,"PING");
printf("PING: %s\n", reply->str);
freeReplyObject(reply);
reply = redisCommand(c,"SET %s %s", "foo", "hello world");
printf("SET: %s\n", reply->str);
freeReplyObject(reply);
reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5);
printf("SET (binary API): %s\n", reply->str);
freeReplyObject(reply);
reply = redisCommand(c,"GET foo");
printf("GET foo: %s\n", reply->str);
freeReplyObject(reply);
reply = redisCommand(c,"INCR counter");
printf("INCR counter: %lld\n", reply->integer);
freeReplyObject(reply);
reply = redisCommand(c,"INCR counter");
printf("INCR counter: %lld\n", reply->integer);
freeReplyObject(reply);
reply = redisCommand(c,"DEL mylist");
freeReplyObject(reply);
for (j = 0; j < 10; j++) {
char buf[64];
snprintf(buf,64,"%d",j);
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
freeReplyObject(reply);
}
reply = redisCommand(c,"LRANGE mylist 0 -1");
if (reply->type == REDIS_REPLY_ARRAY) {
for (j = 0; j < reply->elements; j++) {
printf("%u) %s\n", j, reply->element[j]->str);
}
}
freeReplyObject(reply);
return 0;
}
result:
dcmvrh12% ./example
PING: PONG
SET: OK
SET (binary API): OK
GET foo: hello world
INCR counter: 9
INCR counter: 10
0) element-9
1) element-8
2) element-7
3) element-6
4) element-5
5) element-4
6) element-3
7) element-2
8) element-1
9) element-0
refer to:https://github.com/redis/hiredis/