博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis 客户端 Hiredis 简介
阅读量:4184 次
发布时间:2019-05-26

本文共 6587 字,大约阅读时间需要 21 分钟。

学习总结一下官方发布的C版本客户端 hiredis,了解hiredis 客户端大致实现细节。在理解代码之间需要了解通信协议的特点,我上一篇转载的文章已经有过介绍,大家可以去看一下。

hiredis 提供了同步、异步访问,异步 API 需要与一些事件库协同工作,主要看一下同步API的实现。

hiredis 与服务端通信的API比较简单,主要有这几个步骤:

  • 建立连接
  • 发送命令
  • 等待结果并处理
  • 释放连接

一、相关数据结构

redisContext 保存连接建立后的上下文。 err 保存错误码,如果为0表示没错,如果非0那么错误说明保存在 errstr 中;fd是连接建立后的套接字;flags表示连接的标识;obuf 保存要向 redis-server 发送的命令内容;reader 用来读取从服务端返回的消息,redisReader中的buf成员用来保存内容;connection_type 表示连接类型,有两种分别是REDIS_CONN_TCPREDIS_CONN_UNIXtimeout 是连接时指定的超时时间。

/* Context for a connection to Redis */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;
/* 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 表示服务器返回结果的类型,比如 REDIS_REPLY_STRING,表示返回一个 string,此时内容就保存在 str 这个成员中,其他成员类似。有下面这几种类型:

#define REDIS_REPLY_STRING 1#define REDIS_REPLY_ARRAY 2#define REDIS_REPLY_INTEGER 3#define REDIS_REPLY_NIL 4#define REDIS_REPLY_STATUS 5#define REDIS_REPLY_ERROR 6

(二)相关 api

建立连接

redisContext *redisConnect(const char *ip, int port);redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);redisContext *redisConnectNonBlock(const char *ip, int port);redisContext *redisConnectBindNonBlock(const char *ip, int port,                                       const char *source_addr);redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,                                                const char *source_addr);redisContext *redisConnectUnix(const char *path);redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);redisContext *redisConnectUnixNonBlock(const char *path);redisContext *redisConnectFd(int fd);

这些 api 都是建立连接的,可以根据需求或者条件选择合适的。连接建立成功返回 redisContext,将连接信息放到这个结构中,通过 err 成员来判断是否建立成功。

发送命令并等待结果

redisCommandredisAppendCommand 系列函数用来向服务器发送命令。

redisCommand -> redisvCommand -> redisvAppendCommand -> __redisAppendCommand

int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {    sds newbuf;    newbuf = sdscatlen(c->obuf,cmd,len);    if (newbuf == NULL) {        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");        return REDIS_ERR;    }    c->obuf = newbuf;    return REDIS_OK;}

这个函数组装发送的命令到redisContextobuf 中,obuf 可以动态扩容,所以不用担心溢出;接下来在函数 redisCommand 中调用 __redisBlockForReply,继而调用 redisGetReply 这个函数来获取返回结果。

int redisGetReply(redisContext *c, void **reply) {    int wdone = 0;    void *aux = NULL;    /* Try to read pending replies */    if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)        return REDIS_ERR;    /* For the blocking context, flush output buffer and read reply */    if (aux == NULL && c->flags & REDIS_BLOCK) {        /* Write until done */        do {            if (redisBufferWrite(c,&wdone) == REDIS_ERR)                return REDIS_ERR;        } while (!wdone);        /* Read until there is a reply */        do {            if (redisBufferRead(c) == REDIS_ERR)                return REDIS_ERR;            if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)                return REDIS_ERR;        } while (aux == NULL);    }    /* Set reply object */    if (reply != NULL) *reply = aux;    return REDIS_OK;}

这个函数分下面几步实现:

- 首先查看 redisReader 结构中的 buf 成员是否有数据,有的话则解析出 reply 并返回,否则进入下面;
- 把 obuf 缓冲区的数据全部写入 c->fd,写完后释放 obuf
- 接下来阻塞读取服务器返回的结果并将其放入redisContext -> redisReader -> buf 缓冲区中,进入下一步;
- 调用 redisGetReplyFromReader 解析 buf 中的数据,并返回reply;获取到的 redisReply 对象需要调用 freeReplyObject 显式释放,否则会泄漏。

redisAppendCommand 这个函数将发送的命令格式化放入 redisContextobuf 中,可以一次发送多条命令,然后接收一批结果,结果按照发送命令的顺序保存,这里利用了 pipeline 特性,可以减少网络传输的次数,提高IO吞吐量。

释放连接

使用完一个连接后需要调用 redisFree 函数来释放这个连接,主要是关闭套接字,释放申请的缓冲区。

void redisFree(redisContext *c) {    if (c == NULL)        return;    if (c->fd > 0)        close(c->fd);    sdsfree(c->obuf);    redisReaderFree(c->reader);    free(c->tcp.host);    free(c->tcp.source_addr);    free(c->unix_sock.path);    free(c->timeout);    free(c);}

一个例子

下面给出一个例子,对这几个API最基本的使用,测试的时候需要先安装 redis-server 并启动,默认端口为 6379,同时需要安装 hiredis 库,sudo yum install hiredis-devel,或者源码安装。

/*************************************************************************    > File Name: redis-cli.c    > Author: Tanswer_    > Mail: 98duxm@gmail.com    > Created Time: Thu Jun 28 15:49:43 2018 ************************************************************************/#include 
#include
#include
#include
int main(){ redisContext* c = redisConnect((char*)"127.0.0.1", 6379); if(c->err){ redisFree(c); return 0; } printf("connect redis-server success.\n"); const char* command = "set good luck"; redisReply* r = (redisReply*)redisCommand(c, command); if(r == NULL){ redisFree(c); return 0; } if(!(r->type == REDIS_REPLY_STATUS && strcasecmp(r->str, "OK") == 0)){ printf("Failed to execute command[%s].\n", command); freeReplyObject(r); redisFree(c); return 0; } freeReplyObject(r); printf("Succeed to execute command[%s].\n", command); const char* command1 = "strlen good"; r = (redisReply*)redisCommand(c, command1); if(r->type != REDIS_REPLY_INTEGER){ printf("Failed to execute command[%s].\n", command1); freeReplyObject(r); redisFree(c); return 0; } int length = r -> integer; freeReplyObject(r); printf("The length of 'good' is %d.\n", length); printf("Succeed to execute command[%s].\n", command1); const char* command2 = "get good"; r = (redisReply*)redisCommand(c, command2); if(r -> type != REDIS_REPLY_STRING){ printf("Failed to execute command[%s].\n", command2); freeReplyObject(r); redisFree(c); return 0; } printf("The value of 'goo' is %s.\n", r->str); freeReplyObject(r); printf("Succeed to execute command[%s].\n", command2); redisFree(c); return 0;}
你可能感兴趣的文章
(3)教材目录---信息系统项目管理师考试系列
查看>>
商城基础E-R模型图
查看>>
飞翔的小鸟--键盘事件案例
查看>>
一个sql函数group_concat详解
查看>>
根据地址返回坐标位置的百度地图api
查看>>
thinkcmf数据字典
查看>>
gitflow 分支原理
查看>>
使用 PHP 5.4 或者更高版本计算 tiger 哈希值
查看>>
4字节 整数哈希 ----------jenkins 32位Hash算法
查看>>
哈希函数的逆向算法
查看>>
1-3 beanstalkd参数
查看>>
1-4 beanstalkd生产类
查看>>
1-5 beanstalkd消费类
查看>>
1-6 综合案例-生产者消费者
查看>>
织梦cms模板保护技术
查看>>
laravel 课程学习系列一----------------第一章.composer快速入门
查看>>
laravel 课程学习系列二----------------第二章.PHP框架安装之Laravel
查看>>
laravel 课程学习系列三----------------第三章.Artisan控制台
查看>>
git版本控制管理系列-----第四章 GIT基本概念
查看>>
mysql 库级权限、表级权限授权
查看>>