上一小节,分析了client的请求,这一小节主要看处理具体的命令的详细过程:
客户端指令解析完之后,需要执行该指令,执行指令的两个函数为processCommand与call函数,这两个函数除了单纯的执行指令外,还做了许多其他的工作,processCommand中调用了call函数,只分析call函数,其他的工作有点繁琐,等等仔细看完再写另起炉灶。
call函数中调用了具体的执行函数例如:selectCommand,randomkeyCommand,keysCommand等,这里也不一一详解。指令执行完之后,需要将得到的结果集返回给客户端,这部分是如何工作的,下面开始分析。
在networking.c中可以发现许多以addRelpy为前缀的函数名,这些函数都是用来处理各种不同类型的结果的,我们以典型的addReply函数为例,进行分析。

addReply函数
该函数第一步工作就是调用prepareClientToWrite函数为客户端创建一个写文件事件,事件的处理函数即将结果集发送给客户端的函数为sendReplyToClient.

/* This function is called every time we are going to transmit new data
 * to the client. The behavior is the following:
 *
 * If the client should receive new data (normal clients will) the function
 * returns REDIS_OK, and make sure to install the write handler in our event
 * loop so that when the socket is writable new data gets written.
 *
 * If the client should not receive new data, because it is a fake client
 * or a slave, or because the setup of the write handler failed, the function
 * returns REDIS_ERR.
 *
 * Typically gets called every time a reply is built, before adding more
 * data to the clients output buffers. If the function returns REDIS_ERR no
 * data should be appended to the output buffers. */
int prepareClientToWrite(redisClient *c) {
    if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
    if (c->fd <= 0) return REDIS_ERR; /* Fake client */
    if (c->bufpos == 0 && listLength(c->reply) == 0 &&
        (c->replstate == REDIS_REPL_NONE ||
         c->replstate == REDIS_REPL_ONLINE) &&
        aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
        sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
    return REDIS_OK;
}

第二步,就是根据相应的条件,将得到的结果rboj数据存储到buf中或者reply链表中。对于存储的策略:redis优先将数据存储在固定大小的buf中,也就是redisClient结构体buf[REDIS_REPLY_CHUNK_BYTES]里,默认大小为16K。如果有数据没有发送完或c->buf空间不足,就会放到c->reply链表里面,链表每个节点都是内存buf,后来的数据放入最后面。具体的处理函数为_addReplyToBuffer和_addReplyStringToList两个函数。

void addReply(redisClient *c, robj *obj) {  
    if (prepareClientToWrite(c) != REDIS_OK) return;  

    /* This is an important place where we can avoid copy-on-write 
     * when there is a saving child running, avoiding touching the 
     * refcount field of the object if it's not needed. 
     * 
     * If the encoding is RAW and there is room in the static buffer 
     * we'll be able to send the object to the client without 
     * messing with its page. */  
    if (obj->encoding == REDIS_ENCODING_RAW) {//字符串类型  
        //是否能将数据追加到c->buf中  
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)  
            _addReplyObjectToList(c,obj);//添加到c->reply链表中  
    } else if (obj->encoding == REDIS_ENCODING_INT) {//整数类型  
        /* Optimization: if there is room in the static buffer for 32 bytes 
         * (more than the max chars a 64 bit integer can take as string) we 
         * avoid decoding the object and go for the lower level approach. */  
         //追加到c->buf中  
        if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {  
            char buf[32];  
            int len;  

            len = ll2string(buf,sizeof(buf),(long)obj->ptr);//整型转string  
            if (_addReplyToBuffer(c,buf,len) == REDIS_OK)  
                return;  
            /* else... continue with the normal code path, but should never 
             * happen actually since we verified there is room. */  
        }  
        obj = getDecodedObject(obj);//64位整数,先转换为字符串  
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)  
            _addReplyObjectToList(c,obj);  
        decrRefCount(obj);  
    } else {  
        redisPanic("Wrong obj->encoding in addReply()");  
    }  
}  

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
/** 
    Server将数据发送给Client,有两种存储数据的缓冲形式,具体参见redisClient结构体 
    1、Response buffer 
        int bufpos; //回复 
        char buf[REDIS_REPLY_CHUNK_BYTES]; //长度为16 * 1024 
    2、list *reply; 
        unsigned long reply_bytes; Tot bytes of objects in reply list 
        int sentlen;            已发送的字节数 
    如果已经使用reply的形式或者buf已经不够存储,那么就将数据添加到list *reply中 
    否则将数据添加到buf中 
*/  
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {  
    size_t available = sizeof(c->buf)-c->bufpos;//计算出c->buf的剩余长度  

    if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;  

    /* If there already are entries in the reply list, we cannot 
     * add anything more to the static buffer. */  
    if (listLength(c->reply) > 0) return REDIS_ERR;  

    /* Check that the buffer has enough space available for this string. */  
    if (len > available) return REDIS_ERR;  

    //回复数据追加到buf中  
    memcpy(c->buf+c->bufpos,s,len);  
    c->bufpos+=len;  
    return REDIS_OK;  
}  

/** 
    1、如果链表长度为0: 新建一个节点并直接将robj追加到链表的尾部 
    2、链表长度不为0: 首先取出链表的尾部节点 
        1)、尾部节点的字符串长度 + robj中ptr字符串的长度 <= REDIS_REPLY_CHUNK_BYTES: 
            将robj->ptr追加到尾节点的tail->ptr后面 
        2)、反之: 新建一个节点并直接将robj追加到链表的尾部 
*/  
void _addReplyObjectToList(redisClient *c, robj *o) {  
    robj *tail;  

    if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;  

    //链表长度为0  
    if (listLength(c->reply) == 0) {  
        incrRefCount(o);//增加引用次数  
        listAddNodeTail(c->reply,o);//添加到链表末尾  
        c->reply_bytes += zmalloc_size_sds(o->ptr); //计算o->ptr的占用内存大小  
    } else {  
        //取出链表尾中的数据  
        tail = listNodeValue(listLast(c->reply));  

        /* Append to this object when possible. */  
        // 如果最后一个节点所保存的回复加上新回复内容总长度小于等于 REDIS_REPLY_CHUNK_BYTES  
        // 那么将新回复追加到节点回复当中。  
        if (tail->ptr != NULL &&  
            sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)  
        {  
            c->reply_bytes -= zmalloc_size_sds(tail->ptr);  
            tail = dupLastObjectIfNeeded(c->reply);  
            tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));  
            c->reply_bytes += zmalloc_size_sds(tail->ptr);  
        } else {//为新回复单独创建一个节点  
            incrRefCount(o);  
            listAddNodeTail(c->reply,o);  
            c->reply_bytes += zmalloc_size_sds(o->ptr);  
        }  
    }  
    // 如果突破了客户端的最大缓存限制,那么关闭客户端  
    asyncCloseClientOnOutputBufferLimitReached(c);  
}

sendReplyToClient函数
终于到了最后一步,把c->buf与c->reply中的数据发送给客户端即可,发送同样使用的是最原始的write函数。发送完成之后,redis将当前客户端释放,并且删除写事件,代码比较简单,不详细解释。