Redis C客户端Hiredis代码分析
初始化
redisContext - Redis连接的上下文
/* 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;
每一次连接成功, 都会创建一个redisContext数据, 用于后续的操作
redisReader - 结果读取
typedef struct redisReader {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */ char *buf; /* Read buffer */
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */ redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */ redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
redisReader在redisContext中使用redisReplyObjectFunctions参数进行初始化, 用于读取执行结果, 其中 redisReplyObjectFunctions 是一个函数指针列表, 用于读取不同类型的返回数据,
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createNil)(const redisReadTask*);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
一般使用的是默认的defaultFunctions进行初始化
/* Default set of functions to build the reply. Keep in mind that such a
* function returning NULL is interpreted as OOM. */
static redisReplyObjectFunctions defaultFunctions = {
createStringObject,
createArrayObject,
createIntegerObject,
createNilObject,
freeReplyObject
};
redisReader中的另一个结构体是 redisReadTask数组, 需要配合ridx一起使用, 用于给redisReplyObjectFunctions中的函数提供输入参数
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
在defaultFunctions下面的函数是 createStringObject, createArrayObject, createIntegerObject, createNilObject, freeReplyObject, 这些函数的输入都是rediskReadTask, 但是根据类型不同会有不同的附带参数, 例如array就会有elements元素数量, 而string就会有char *str和size_t len, 都会在处理中产生redisReply对象, 并将这个对象挂载到上一级redisReply对象的element变量上(如果有上一级的话).
结果解析
执行命令时, 实际调用的处理方法是redisvCommand,
void *redisvCommand(redisContext *c, const char *format, va_list ap) {
if (redisvAppendCommand(c,format,ap) != REDIS_OK)
return NULL;
return __redisBlockForReply(c);
}
在这里会调用__redisBlockForReply来处理存在redisContext *c中的结果
/* Helper function for the redisCommand* family of functions.
*
* Write a formatted command to the output buffer. If the given context is
* blocking, immediately read the reply into the "reply" pointer. When the
* context is non-blocking, the "reply" pointer will not be used and the
* command is simply appended to the write buffer.
*
* Returns the reply when a reply was successfully retrieved. Returns NULL
* otherwise. When NULL is returned in a blocking context, the error field
* in the context will be set.
*/
static void *__redisBlockForReply(redisContext *c) {
void *reply; if (c->flags & REDIS_BLOCK) {
if (redisGetReply(c,&reply) != REDIS_OK)
return NULL;
return reply;
}
return NULL;
}
这里会将错误情况排除, 实际的处理方法是 redisGetReply, 这里的处理方式是阻塞的, 如果aux已经读到error就返回, 否则一直在等直到有值
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;
}
在阻塞读取的过程中, 会先通过redisBufferRead尝试读取, 成功后再用redisGetReplyFromReader尝试解析
/* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser.
*
* After this function is called, you may use redisContextReadReply to
* see if there is a reply available. */
int redisBufferRead(redisContext *c) {
char buf[1024*16];
int nread; /* Return early when the context has seen an error. */
if (c->err)
return REDIS_ERR; nread = read(c->fd,buf,sizeof(buf));
if (nread == -1) {
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */
} else {
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
} else if (nread == 0) {
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
return REDIS_ERR;
} else {
if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
}
}
return REDIS_OK;
}
在redisGetReplyFromReader中调用redisReaderGetReply解析, 再往内部, 就是循环调用processItem来处理结果了
/* Internal helper function to try and get a reply from the reader,
* or set an error in the context otherwise. */
int redisGetReplyFromReader(redisContext *c, void **reply) {
if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
}
return REDIS_OK;
} int redisReaderGetReply(redisReader *r, void **reply) {
/* Default target pointer to NULL. */
if (reply != NULL)
*reply = NULL; /* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR; /* When the buffer is empty, there will never be a reply. */
if (r->len == 0)
return REDIS_OK; /* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
r->rstack[0].type = -1;
r->rstack[0].elements = -1;
r->rstack[0].idx = -1;
r->rstack[0].obj = NULL;
r->rstack[0].parent = NULL;
r->rstack[0].privdata = r->privdata;
r->ridx = 0;
} /* Process items in reply. */
while (r->ridx >= 0)
if (processItem(r) != REDIS_OK)
break; /* Return ASAP when an error occurred. */
if (r->err)
return REDIS_ERR; /* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) {
sdsrange(r->buf,r->pos,-1);
r->pos = 0;
r->len = sdslen(r->buf);
} /* Emit a reply when there is one. */
if (r->ridx == -1) {
if (reply != NULL)
*reply = r->reply;
r->reply = NULL;
}
return REDIS_OK;
}
在processItem中, 会根据第一个字节确定当前redisReadTask的类型, 然后对类型为integer, string和array的数据进行处理, 其他的直接返回error
static int processItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
char *p; /* check if we need to read type */
if (cur->type < 0) {
if ((p = readBytes(r,1)) != NULL) {
switch (p[0]) {
case '-':
cur->type = REDIS_REPLY_ERROR;
break;
case '+':
cur->type = REDIS_REPLY_STATUS;
break;
case ':':
cur->type = REDIS_REPLY_INTEGER;
break;
case '$':
cur->type = REDIS_REPLY_STRING;
break;
case '*':
cur->type = REDIS_REPLY_ARRAY;
break;
default:
__redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR;
}
} else {
/* could not consume 1 byte */
return REDIS_ERR;
}
} /* process typed item */
switch(cur->type) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER:
return processLineItem(r);
case REDIS_REPLY_STRING:
return processBulkItem(r);
case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r);
default:
assert(NULL);
return REDIS_ERR; /* Avoid warning. */
}
}
对redisReadTask队列和ridx的操作只发生在类型为array的情况下, 是在processMultiBulkItem的处理中进行的, 这里会取出一整个这一层array里的数据, 如果遇到元素的elements大于0的, 说明这个元素也是一个数组, 那么就会ridx加一, 在rstack中增加一个redisReadTask, 这时候就不再继续处理同层数据了, 而是等着外循环去进入下一层处理.
static int processMultiBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
long long elements;
int root = 0, len; /* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"No support for nested multi bulk replies with depth > 7");
return REDIS_ERR;
} if ((p = readLine(r,&len)) != NULL) {
if (string2ll(p, len, &elements) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad multi-bulk length");
return REDIS_ERR;
} root = (r->ridx == 0); if (elements < -1 || elements > INT_MAX) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range");
return REDIS_ERR;
} if (elements == -1) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL; if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
} moveToNextTask(r);
} else {
if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements);
else
obj = (void*)REDIS_REPLY_ARRAY; if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
} /* Modify task stack when there are more than 0 elements. */
if (elements > 0) {
cur->elements = elements;
cur->obj = obj;
r->ridx++;
r->rstack[r->ridx].type = -1;
r->rstack[r->ridx].elements = -1;
r->rstack[r->ridx].idx = 0;
r->rstack[r->ridx].obj = NULL;
r->rstack[r->ridx].parent = cur;
r->rstack[r->ridx].privdata = r->privdata;
} else {
moveToNextTask(r);
}
} /* Set reply if this is the root object. */
if (root) r->reply = obj;
return REDIS_OK;
} return REDIS_ERR;
}
这里要注意的是moveToNextTask这个函数, 这是去移动到当前层的下一个元素, 如果当前层已经空了就直接返回, 如果在当前层的编号已经到最后一个, 就返回上一层. 所以这个hiredis对返回数组的处理是一个深度遍历, 并且规定了层数不能超过8
static void moveToNextTask(redisReader *r) {
redisReadTask *cur, *prv;
while (r->ridx >= 0) {
/* Return a.s.a.p. when the stack is now empty. */
if (r->ridx == 0) {
r->ridx--;
return;
} cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
/* Reset the type because the next item can be anything */
assert(cur->idx < prv->elements);
cur->type = -1;
cur->elements = -1;
cur->idx++;
return;
}
}
}
.
Redis C客户端Hiredis代码分析的更多相关文章
- Redis 发布/订阅机制原理分析
Redis 通过 PUBLISH. SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能. 这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播.实时 ...
- Hive metastore整体代码分析及详解
从上一篇对Hive metastore表结构的简要分析中,我再根据数据设计的实体对象,再进行整个代码结构的总结.那么我们先打开metadata的目录,其目录结构: 可以看到,整个hivemeta的目录 ...
- 【原创】自己动手写一个能操作redis的客户端
引言 redis大家在项目中经常会使用到.官网也提供了多语言的客户端供大家操作redis,如下图所示 但是,大家有思考过,这些语言操作redis背后的原理么?其实,某些大神会说 只要按照redis的协 ...
- Redis网络模型的源码分析
Redis的网络模型是基于I/O多路复用程序来实现的.源码中包含四种多路复用函数库epoll.select.evport.kqueue.在程序编译时会根据系统自动选择这四种库其中之一.下面以epoll ...
- 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
- Kafka Producer相关代码分析【转】
来源:https://www.zybuluo.com/jewes/note/63925 @jewes 2015-01-17 20:36 字数 1967 阅读 1093 Kafka Producer相关 ...
- lighttpd与fastcgi+cgilua原理、代码分析与安装
原理 http://www.cnblogs.com/skynet/p/4173450.html 快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关 ...
- 在使用Redis的客户端连接工具ServiceStack.Redis要注意的问题
在使用Redis的客户端连接工具ServiceStack.Redis要注意的问题 Redis是一个非常NB的内存级的数据库,我们可以把很多”热数据“(即读写非常多的数据)放入其中来操作,这样就减少 ...
- Redis C客户端API - God's blog - 博客频道 - CSDN.NET
Redis C客户端API - God's blog - 博客频道 - CSDN.NET Redis安装步骤: 1.redis server安装 wget http://redis.googlecod ...
随机推荐
- OA系统权限管理设计方案【转】
l 不同职责的人员,对于系统操作的权限应该是不同的.优秀的业务系统,这是最基本的功能. l 可以对“组”进行权限分配.对于一个大企业的业务系统来说,如果要求管理员为其下员工逐一分配系统操作权限的话,是 ...
- DevExpress ChartControl控件实现图表【转】
1.饼状图图 1.1添加ChartControl控件 在工具箱中找到ChartControl控件,拖到窗口中,创建Pie: 1.2准备数据 private DataTable CreateChartD ...
- 【Scala】Scala-调用Java-集合
Scala-调用Java-集合 sacla 遍历 java list_百度搜索 13.11 Scala混用Java的集合类调用scala的foreach遍历问题 - 简书
- ML&DL视频教程资源
作者:Bruce链接:https://www.zhihu.com/question/49909565/answer/345894856来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载 ...
- [Spring Boot] Complex Scope Scenarios of a Spring Bean - Mix Prototype and Singleton, ScopeProxy
We have the following example: @SpringBootApplication public class In28minutesScopeApplication { pri ...
- 大量带BPM的跑步歌曲/跑步音乐下载
20150110停止更新告知:不知不觉本帖更新有近半年了.从最开始跑步已经四年多,一直是听着音乐跑的,音乐支持.陪伴.丰富着我的跑步之旅.直到上个月因一次觉得音乐吵,我开始有意地摘掉耳机去跑步,并开始 ...
- 通过项目逐步深入了解Mybatis<二>
Mybatis 解决 jdbc 编程的问题 1. 数据库链接创建.释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题. 解决:在SqlMapConfig.xml中配置数据链接池 ...
- python抓取数据,python使用socks代理抓取数据
在python中,正常的抓取数据直接使用urllib2 这个模块: import urllib2 url = 'http://fanyi.baidu.com/' stream = urllib2.ur ...
- Linux环境下网络编程杂谈《转》
今天我们说说“Pre-网络编程”.内容比较杂,但都是在做网络应用程序开发过程中经常要遇到的问题. 一.大端.小端和网络字节序 小端字节序:little-endian,将低字节存放在内存的起始地址: 大 ...
- js - object.assign 以及浅、深拷贝
浅(引用)拷贝:共用同一内存地址,你改值我也变 譬如常用的对象赋值操作 深拷贝:深拷贝即创建新的内存地址保存值(互不影响) 譬如以下 const shallBasicCopy = obj => ...