1. 程式人生 > >Redis原始碼解析:18Hiredis同步API和回覆解析API程式碼解析

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