Redis原始碼解析:18Hiredis同步API和回覆解析API程式碼解析
Redis的sentinel模式使用了Hiredis程式碼,Hiredis是redis資料庫一個輕量級的C語言客戶端庫。它實現的向Redis傳送命令的API函式redisCommand,使用方法類似於printf。因此只要熟悉redis命令,就可以很容易的使用該函式將redis命令字串,轉換成統一請求協議格式之後,傳送給Redis伺服器。
Hiredis庫包含三類API:同步操作API、非同步操作API和回覆解析API。本文主要介紹同步操作API和回覆解析API,下一篇介紹非同步操作API。
一:同步操作API
所謂的同步操作,就是以阻塞的方式向Redis伺服器建鏈,傳送命令,接收命令回覆。使用同步操作API,主要涉及以下三個API函式:
redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);
1:TCP建鏈
redisConnect函式建立一個上下文結構redisContext,並向Redis伺服器發起TCP建鏈。該函式是同步建鏈API,因此該函式返回後,要麼TCP已經建鏈成功了,要麼建鏈期間發生了錯誤,可以通過檢查redisContext結構的err和errstr屬性得到錯誤型別和錯誤型別。
redisConnect的程式碼較簡單,如下:
redisContext *redisConnect(const char *ip, int port) {
redisContext *c;
c = redisContextInit();
if (c == NULL)
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,NULL);
return c;
}
redisContext上下文結構用於儲存所有與Redis伺服器連線的狀態。比如socket描述符,輸出快取,回覆解析器等。該結構的定義如下:
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 */
} redisContext;
屬性err為非0時,表示與Redis伺服器的連線發生了錯誤,屬性errstr就包含一個描述該錯誤的字串。因此,每次與Redis進行互動之後,就需要檢查該屬性判斷是否發生了錯誤,一旦有錯誤發生,則立即終止與Redis的連結。
屬性fd就是與Redis伺服器連結的socket描述符;flags表示客戶端標誌位,表示客戶端當前的狀態;
obuf就是輸出快取,當用戶呼叫redisCommand向Redis傳送命令時,命令字串首先就是追加到該輸出快取中;
reader是一個回覆解析器,後續在“回覆解析API”中會詳細介紹。
2:傳送命令,接收回復
使用者可以呼叫redisCommand函式向Redis伺服器傳送命令,該函式返回Redis的回覆資訊。該函式的原型如下:
void *redisCommand(redisContext *c, const char *format, ...)
該函式類似於printf,支援不定引數,使用起來很方便,比如:
reply = redisCommand(context, "SET foo bar");
redisCommand函式返回NULL表示發生了錯誤,可以通過檢查redisContext結構中的err得到錯誤型別;如果執行成功,則返回值是一個指向redisReply結構的指標,其中包含了Redis的回覆資訊。
可以在格式字串中使用”%s”,表示在命令中插入一個字串,此時使用strlen判斷字串的長度:
reply = redisCommand(context, "SET foo %s", value);
如果需要在命令中傳遞二進位制安全的字串,可以使用”%b”,此時需要一個size_t型別的引數表示該字串的長度:
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
redisCommand主要是通過redisvCommand實現的,而redisvCommand主要是通過redisvAppendCommand和__redisBlockForReply兩個函式實現。它們的程式碼如下:
void *redisvCommand(redisContext *c, const char *format, va_list ap) {
if (redisvAppendCommand(c,format,ap) != REDIS_OK)
return NULL;
return __redisBlockForReply(c);
}
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;
}
redisvAppendCommand函式作用是解析使用者的輸入,並將使用者輸入的命令字串轉換成Redis統一請求協議的格式,儲存到上下文結構redisContext中的輸出快取obuf中,它的程式碼如下:
int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
char *cmd;
int len;
len = redisvFormatCommand(&cmd,format,ap);
if (len == -1) {
__redisSetError(c,REDIS_ERR_OOM,"Out of memory");
return REDIS_ERR;
}
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
free(cmd);
return REDIS_ERR;
}
free(cmd);
return REDIS_OK;
}
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;
}
redisvAppendCommand首先呼叫redisvFormatCommand函式,用於解析使用者輸入的命令字串,並將字串轉換成協議格式之後,儲存在cmd中。然後呼叫__redisAppendCommand函式,將cmd追加到輸出快取c->obuf中。程式碼較簡單,不在贅述。
在redisvCommand函式中,呼叫redisvAppendCommand之後,接下來就是呼叫__redisBlockForReply函式,將輸出快取中的內容傳送給Redis伺服器,並讀取Redis的回覆,並解析之。
__redisBlockForReply函式主要是通過redisGetReply實現的,它們的程式碼如下:
static void *__redisBlockForReply(redisContext *c) {
void *reply;
if (c->flags & REDIS_BLOCK) {
if (redisGetReply(c,&reply) != REDIS_OK)
return NULL;
return reply;
}
return NULL;
}
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;
}
在redisGetReply中,首先是迴圈呼叫redisBufferWrite,將輸出快取c->obuf中的所有內容傳送給Redis。然後迴圈呼叫redisBufferRead,讀取Redis的回覆,並呼叫函式redisGetReplyFromReader對回覆資訊進行解析。
redisBufferRead函式的程式碼如下:
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;
}
該函式主要是從socket中讀取資料到buf中,然後通過函式redisReaderFeed,將buf內容追加到解析器的輸入快取中。redisReaderFeed函式屬於回覆解析API函式。
二:回覆解析API
回覆解析API的函式主要有下面幾個:
redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *reader);
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
int redisReaderGetReply(redisReader *reader, void **reply);
1:輸入快取
解析器結構redisReader,是回覆解析API最主要的資料結構。它的部分定義如下:
/* State for the protocol parser */
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 */
...
} redisReader;
其中的err和errstr屬性與redisContext結構中的err和errstr屬性作用一樣,都是用於儲存錯誤型別和錯誤資訊的;
buf屬性就是輸入快取,redisReaderFeed函式將讀取到的Redis回覆資訊都儲存到該快取中,該快取根據回覆資訊可以動態擴容。len表示當前快取的容量;pos表示快取當前的讀取索引,每次讀取輸入快取時,都是從reader->buf + reader->pos處開始讀取,讀取資料之後,會增加pos的值;
maxbuf屬性表示輸入快取所允許的最大閒置空間。為了節省記憶體空間,當buf為空,並且當前buf的閒置空間大於reader->maxbuf時,就會釋放r->buf,重新為其申請空間。該屬性的預設值為16K。如果置為0,表示無此限制。
redisReaderFeed就是將從socket讀取的Redis回覆資訊,追加到輸入快取的函式。其程式碼如下:
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
sds newbuf;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* Copy the provided buffer. */
if (buf != NULL && len >= 1) {
/* Destroy internal buffer when it is empty and is quite large. */
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf);
r->buf = sdsempty();
r->pos = 0;
/* r->buf should not be NULL since we just free'd a larger one. */
assert(r->buf != NULL);
}
newbuf = sdscatlen(r->buf,buf,len);
if (newbuf == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->buf = newbuf;
r->len = sdslen(r->buf);
}
return REDIS_OK;
}
2:解析
在redisGetReply函式中,將Redis的回覆資訊追加到解析器輸入快取之後,接下來就會呼叫函式redisGetReplyFromReader對解析器的輸入快取中的訊息進行解析,解析的內容以redisReply結構進行組織。
如果回覆資訊是巢狀的話,則形成一顆以redisReply結構為節點的多叉樹;如果回覆資訊只是基本資訊的話,則該樹僅僅包含一個根節點。redisCommand函式就是返回一個指向redisReply結構樹根節點的指標。redisReply結構樹的寬度沒有限制,但是深度的最大值為7,也就是僅允許最多7層巢狀。
首先看一下redisReply結構的定義如下:
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
int type;
long long integer;
int len;
char *str;
size_t elements;
struct redisReply **element;
} redisReply;
該結構中的type成員表示Redis回覆資訊的型別,可以有下面幾種型別:
REDIS_REPLY_STATUS:狀態回覆,狀態資訊以'+'開頭。str屬性儲存Redis回覆的狀態資訊字串,該字串的長度儲存在len屬性中。
REDIS_REPLY_ERROR:錯誤回覆,錯誤資訊以'-'開頭。str屬性儲存Redis回覆的錯誤資訊字串,該字串的長度儲存在len屬性中。
REDIS_REPLY_INTEGER:整數回覆,整數資訊以':'開頭。integer 屬性儲存Redis回覆的整數值。
REDIS_REPLY_STRING:單行字串回覆,這種資訊以'$'開頭。str屬性儲存Redis回覆的字串資訊,該字串的長度儲存在len屬性中。
REDIS_REPLY_NIL:Redis回覆”nil”。
以上的型別可以稱為基本型別。
REDIS_REPLY_ARRAY:陣列回覆,也就是巢狀回覆,陣列資訊以'*'開頭,後接陣列元素個數。陣列中的元素可以是以上所有基本型別,也可以是REDIS_REPLY_ARRAY型別,也就是陣列巢狀陣列。
陣列元素的個數儲存在elements屬性中,陣列元素也以redisReply結構表示,指向陣列元素的指標儲存在element指標陣列中,也就是說,指標陣列element中儲存了所有孩子節點的指標。
經過回覆解析API函式redisReaderGetReply的解析之後,最終形成的redisReply結構樹,非葉子節點只能是REDIS_REPLY_ARRAY型別,葉子節點的型別只能是基本型別。
處理陣列資訊的程式碼較複雜,以一個例子說明。假設Redis的回覆資訊是:"*3\r\n*3\r\n:11\r\n:12\r\n:13\r\n*3\r\n:21\r\n:22\r\n:23\r\n:31\r\n"。
分析該字串,第一個字元為”*”,表明這是一條陣列回覆,後面的3表示陣列元素的個數,因此,最終形成的樹,根節點有3個孩子節點。
接下來就是根節點各個孩子節點的資訊。第一個孩子節點首字元還是”*”,說明該孩子節點又是一個數組資訊,它也有3個孩子。接下來就是3個孩子資訊,也就是3個整數:11,12和13。
接下來是根節點第二個孩子節點的資訊。首字元還是”*”,說明該孩子節點也是一個數組,它也有3個孩子,分別是整數:21,22和23。
接下來是根節點最後一個孩子的資訊,首字元是”:”,說明該孩子節點是一個整數,整數值為31。
根據以上的分析,最終形成的樹如下圖:
上圖中,非葉子節點都是REDIS_REPLY_ARRAY型別的redisReply結構,葉子節點是REDIS_REPLY_INTEGER型別的redisReply結構。
回覆解析API函式redisReaderGetReply的作用,就是解析回覆資訊,最終形成一顆這樣的redisReply結構樹。
在回覆解析API的程式碼中,使用redisReadTask任務結構解析回覆資訊,構建每個redisReply結構節點,填充到樹中合適的位置。
redisReadTask結構包含解析器結構redisReader中,redisReader結構剩下的定義如下:
typedef struct redisReader {
...
redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
在redisReader結構中,redisReadTask結構陣列rstack大小為9。rstack[0]用於處理redisReply結構樹中的根節點;rstack[1]表示處理redisReply結構樹中的第一層子節點,以此類推。
ridx屬性表示當前正在處理第幾層子節點;fn屬性是一個redisReplyObjectFunctions結構體,該結構中包含了用於生成各種型別redisReply結構的函式;
reply屬性指向redisReply結構樹中的根節點
構建每個redisReply結構節點的redisReadTask結構定義如下:
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;
type表示該redisReadTask結構當前處理的資訊型別,與其當前構建的redisReply結構節點中的type一致;
elements表示當前構建的REDIS_REPLY_ARRAY型別的redisReply結構節點中,包含的子節點數目。也就是redisReply結構節點中,陣列element中的元素個數;idx表示當前構建的redisReply結構節點,在其父節點element陣列中的索引;obj就是指向當前正在構建的REDIS_REPLY_ARRAY 型別的redisReply結構節點,parent表示正在處理當前節點的父節點的redisReadTask結構。
回覆解析API函式redisReaderGetReply的程式碼如下:
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;
}
首先,將r->ridx置為0,然後初始化r->rstack[0],表示接下來開始構建根節點。
接下來的語句,就是迴圈呼叫processItem函式,直到r->ridx再次等於-1。迴圈呼叫processItem函式的過程,就是以深度優先的順序構建redisReply結構樹的過程。
processItem函式的程式碼如下:
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結構cur,然後從輸入快取中讀取首個字元,以判斷接下來的回覆資訊的型別,賦值到cur->type中。
得到型別資訊之後,就呼叫不同的函式處理不同的型別。首先看一下處理陣列型別的函式processMultiBulkItem的實現:
static int processMultiBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
long elements;
int root = 0;
/* 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,NULL)) != NULL) {
elements = readLongLong(p);
root = (r->ridx == 0);
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;
}
首先得到構建當前節點的redisReadTask結構cur,然後呼叫readLine函式,從輸入快取中讀取一行資訊(”\r\n”之前的內容),並解析出當前節點中包含的元素個數elements。
如果elements不是-1,說明正確解析到了陣列元素個數,接下來呼叫r->fn->createArray建立一個數組型別的redisReply結構節點。然後將建立的redisReply結構資訊記錄到cur中:將元素個數記錄到cur->elements中,將建立的redisReply記錄到cur->obj中:
cur->elements = elements;
cur->obj = obj;
陣列型別的redisReply結構節點建立完成後,接下來就是開始構建其各個子節點。首先就是將r->ridx加1,並初始化r->rstack[r->ridx]結構,注意這裡置r->rstack[r->ridx].idx為0.表示接下來首先構建第一個子節點。
下面是建立陣列型別redisReply結構的函式createArrayObject的程式碼:
static void *createArrayObject(const redisReadTask *task, int elements) {
redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_ARRAY);
if (r == NULL)
return NULL;
if (elements > 0) {
r->element = calloc(elements,sizeof(redisReply*));
if (r->element == NULL) {
freeReplyObject(r);
return NULL;
}
}
r->elements = elements;
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
return r;
}
該函式中,重點是要理解下面的程式碼:
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
這段程式碼的作用,就是將指向新建立的redisReply結構節點的指標r,存放到其父節點的element陣列中,存放索引就是task->idx。
如果task->parent不為NULL,說明當前新建的redisReply結構節點具有父節點,根據當前task得到該父節點redisReply結構parent。然後將當前節點放到儲存到父節點的element陣列中的task-idx索引處。
接下來是moveToNextTask函式的實現,該函式的主要作用,實際上是變更屬性r->ridx和cur->idx。說白了,就是為下一個要建立的節點,找到合適的位置。程式碼如下:
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;
}
}
}
在while迴圈中,首先得到處理當前節點的redisReadTask結構cur,然後是正處理該節點父節點的redisReadTask結構prv。
cur->idx記錄了當前處理的節點,其在父節點中的element陣列中的索引,也就是當前節點是父節點的第幾個孩子。
prv->elements表示當前節點的父節點,共有幾個孩子。
因此,如果cur->idx小於prv->elements的話,則接下來,cur結構要開始構建當前節點的下一個兄弟節點了,因此將cur->idx加1。
如果cur->idx等於prv->elements的話,說明當前節點,已經是其父節點最後一個孩子節點了。接下來,就開始構建當前節點的叔叔結點了(父節點的兄弟節點),因此r->ridx--,表示回溯。上移一層,將父結點變成當前節點,然後接著判斷新的cur點在其父節點中是否是最後一個孩子,若是,則接著回溯,否則開始構建其兄弟節點。
如果當前節點已經是根節點了(r->ridx == 0),因為根節點沒有兄弟節點,因此將r->ridx置為-1後,直接返回。
構建好一顆redisReply結構樹之後,如果需要釋放它,可以通過API函式freeReplyObject實現,程式碼如下:
void freeReplyObject(void *reply) {
redisReply *r = reply;
size_t j;
switch(r->type) {
case REDIS_REPLY_INTEGER:
break; /* Nothing to free */
case REDIS_REPLY_ARRAY:
if (r->element != NULL) {
for (j = 0; j < r->elements; j++)
if (r->element[j] != NULL)
freeReplyObject(r->element[j]);
free(r->element);
}
break;
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
if (r->str != NULL)
free(r->str);
break;
}
free(r);
}
標準的深度優先順序進行釋放。不再贅述。
以上就是回覆解析API的主要工作流程。構建redisReply結構樹和redisReadTask結構的作用比較晦澀,但是卻是一個很好的構建多叉樹的例子。學習程式碼時,腦子中跟著程式碼逐步建立這顆樹就好理解了。
參考:
http://blog.csdn.net/it_small_farmer/article/details/41726293