《redis设计与实现》--客户端如何发送及接受命令

一 序

   上一篇整理了redis的server端流程,本篇文章介绍的是 Redis 客户端如何处理输入的命令、向服务发送命令以及取得服务端回复并输出到终端等过程。 简单的交互过程如下:

一、Client 发起socket 连接
二、Server 接受socket连接
三、客户端 开始写入
四、server 端接收写入
五、server 返回写入结果
六、Client收到返回结果

 详细调用是大图镇楼:图片来自,https://www.jianshu.com/p/9ed98c7cbe6b

相关知识点:Linux Socket 建立流程和epoll I/O 多路复用技术。

先看下客户端的相关属性。源码在redis-cli.c

   static redisContext *context;(hiredis.h  用来保存与redis服务器连接状态相关信息、输出缓冲区以及回复解析器)
   static struct config (源码在redis_cli.c)会初始化 config 全局变量,该变量记录了客户端几乎所有的配置参数信息,
而 context 用于连接 redis 服务器。看一下 config 的结构.

static struct config {
    char *hostip;
    int hostport;
    char *hostsocket;
    long repeat;
    long interval;
    int dbnum;
    int interactive;
    int shutdown;
    int monitor_mode;
    int pubsub_mode;
    int latency_mode;
    int latency_dist_mode;
    int latency_history;
    int lru_test_mode;
    long long lru_test_sample_size;
    int cluster_mode;
    int cluster_reissue_command;
    int slave_mode;
    int pipe_mode;
    int pipe_timeout;
    int getrdb_mode;
    int stat_mode;
    int scan_mode;
    int intrinsic_latency_mode;
    int intrinsic_latency_duration;
    char *pattern;
    char *rdb_filename;
    int bigkeys;
    int stdinarg; /* get last arg from stdin. (-x option) */
    char *auth;
    int output; /* output mode, see OUTPUT_* defines */
    sds mb_delim;
    char prompt[128];
    char *eval;
    int eval_ldb;
    int eval_ldb_sync;  /* Ask for synchronous mode of the Lua debugger. */
    int eval_ldb_end;   /* Lua debugging session ended. */
    int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
    int last_cmd_type;
} config;

redisContext 源码在(hiredis.h) 

/* 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 */
} redisContext;

二 、Client 发起socket 连接

  我们也从main开始,

int main(int argc, char **argv) {
    int firstarg;

    config.hostip = sdsnew("127.0.0.1");
    config.hostport = 6379;
    config.hostsocket = NULL;
    config.repeat = 1;
    config.interval = 0;
    config.dbnum = 0;
    config.interactive = 0;
    config.shutdown = 0;
    config.monitor_mode = 0;
    config.pubsub_mode = 0;
    config.latency_mode = 0;
    config.latency_dist_mode = 0;
    config.latency_history = 0;
    config.lru_test_mode = 0;
    config.lru_test_sample_size = 0;
    config.cluster_mode = 0;
    config.slave_mode = 0;
    config.getrdb_mode = 0;
    config.stat_mode = 0;
    config.scan_mode = 0;
    config.intrinsic_latency_mode = 0;
    config.pattern = NULL;
    config.rdb_filename = NULL;
    config.pipe_mode = 0;
    config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
    config.bigkeys = 0;
    config.stdinarg = 0;
    config.auth = NULL;
    config.eval = NULL;
    config.eval_ldb = 0;
    config.eval_ldb_end = 0;
    config.eval_ldb_sync = 0;
    config.enable_ldb_on_eval = 0;
    config.last_cmd_type = -1;

    pref.hints = 1;

    spectrum_palette = spectrum_palette_color;
    spectrum_palette_size = spectrum_palette_color_size;

    if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
        config.output = OUTPUT_RAW;
    else
        config.output = OUTPUT_STANDARD;
    config.mb_delim = sdsnew("\n");

    firstarg = parseOptions(argc,argv);
    argc -= firstarg;
    argv += firstarg;

    /* Latency mode */
    if (config.latency_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        latencyMode();
    }

    /* Latency distribution mode */
    if (config.latency_dist_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        latencyDistMode();
    }

    /* Slave mode */
    if (config.slave_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        slaveMode();
    }

    /* Get RDB mode. */
    if (config.getrdb_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        getRDB();
    }

    /* Pipe mode */
    if (config.pipe_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        pipeMode();
    }

    /* Find big keys */
    if (config.bigkeys) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        findBigKeys();
    }

    /* Stat mode */
    if (config.stat_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        if (config.interval == 0) config.interval = 1000000;
        statMode();
    }

    /* Scan mode */
    if (config.scan_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        scanMode();
    }

    /* LRU test mode */
    if (config.lru_test_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        LRUTestMode();
    }

    /* Intrinsic latency mode */
    if (config.intrinsic_latency_mode) intrinsicLatencyMode();

    /* Start interactive mode when no command is provided */
    if (argc == 0 && !config.eval) {
        /* Ignore SIGPIPE in interactive mode to force a reconnect */
        signal(SIGPIPE, SIG_IGN);

        /* Note that in repl mode we don't abort on connection error.
         * A new attempt will be performed for every command send. */
        cliConnect(0);
        repl();
    }

    /* Otherwise, we have some arguments to execute */
    if (cliConnect(0) != REDIS_OK) exit(1);
    if (config.eval) {
        return evalMode(argc,argv);
    } else {
        return noninteractive(argc,convertToSds(argc,argv));
    }
}

可以看到Redis 中有好多模式,包括:Latency、Slave、Pipe、Stat、Scan、LRU test 等等模式,不过这些模式都不是这篇文章关注的重点,我们只会关注最常见的 repl 模式。主要的函数有:

      cliConnect(0);
        repl();
/* Connect to the server. If force is not zero the connection is performed
 * even if there is already a connected socket. */
//连接,非0为强制执行
static int cliConnect(int force) {
    if (context == NULL || force) {
        if (context != NULL) {
            redisFree(context);
        }

        if (config.hostsocket == NULL) {//TCP Socket连接
            context = redisConnect(config.hostip,config.hostport);
        } else {//Unix Socket连接
            context = redisConnectUnix(config.hostsocket);
        }

        if (context->err) {//连接失败
            fprintf(stderr,"Could not connect to Redis at ");
            if (config.hostsocket == NULL)
                fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
            else
                fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
            redisFree(context);
            context = NULL;
            return REDIS_ERR;
        }

        /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
         * in order to prevent timeouts caused by the execution of long
         * commands. At the same time this improves the detection of real
         * errors. */
        anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);

        /* Do AUTH and select the right DB. */
        //认证
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        //选择db    
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
    }
    return REDIS_OK;
}

cliConnect主要包含redisConnect、redisConnectUnix方法。这两个方法分别用于TCP Socket连接以及Unix Socket连接,Unix Socket用于同一主机进程间的通信。我们上面是采用的TCP Socket连接方式也就是我们平常生产环境常用的方式,这里不讨论Unix Socket连接方式,当然cliConnect方法中还会调用cliAuth方法用于权限验证、cliSelect用于db选择,这里不展开讨论。

看下redisConnect,源码在hiRedis.c

/* Connect to a Redis instance. On error the field error in the returned
 * context will be set to the return value of the error function.
 * When no set of reply functions is given, the default set will be used. */
 //连接redis,参数是ip跟端口号
redisContext *redisConnect(const char *ip, int port) {
    redisContext *c;
    //创建redisContext
    c = redisContextInit();
    if (c == NULL)
        return NULL;

    c->flags |= REDIS_BLOCK;
    //向reids服务器发起连接请求
    redisContextConnectTcp(c,ip,port,NULL);
    return c;
}

具体实现调用了redisContextConnectTcp(),源码在net.c,开始获取IP地址和端口用于建立连接,主要方法如下:

static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
                                   const struct timeval *timeout,
                                   const char *source_addr) {
...
s = socket(p->ai_family,p->ai_socktype,p->ai_protocol

connect(s,p->ai_addr,p->ai_addrlen)
...
}

到此客户端向服务端发起建立socket连接,并且等待服务器端响应。

当服务器连接成功时,context 的 fd 为连接成功后的 sockfd,flags 设置为 REDIS_CONNECTED,

三 Server 接受socket连接

    服务器接收客户端的请求首先是从epoll_wait取出相关的事件,然后执行acceptTcpHandler或者acceptUnixHandler方法,那么这两个方法对应的事件是在什么时候注册的呢?他们是在服务器端初始化的时候创建。具体参见:https://blog.csdn.net/bohu83/article/details/85009255

  因为不是客户端的主要流程,本篇不展开。

四 客户端开始写入

   客户端在与服务器端建立好socket连接之后,进入交互模式,看下函数repl(),源码在redis_cli.c。

static void repl(void) {
    sds historyfile = NULL;
    int history = 0;
    char *line;
    int argc;
    sds *argv;

    /* Initialize the help and, if possible, use the COMMAND command in order
     * to retrieve missing entries. */
    cliInitHelp();
    cliIntegrateHelp();

    config.interactive = 1;
    linenoiseSetMultiLine(1);
    linenoiseSetCompletionCallback(completionCallback);
    linenoiseSetHintsCallback(hintsCallback);
    linenoiseSetFreeHintsCallback(freeHintsCallback);

    /* Only use history and load the rc file when stdin is a tty. */
    if (isatty(fileno(stdin))) {
        historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
        if (historyfile != NULL) {
            history = 1;
            linenoiseHistoryLoad(historyfile);
        }
        cliLoadPreferences();
    }

    cliRefreshPrompt();
    while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
        if (line[0] != '\0') {
            argv = cliSplitArgs(line,&argc);
            if (history) linenoiseHistoryAdd(line);
            if (historyfile) linenoiseHistorySave(historyfile);

            if (argv == NULL) {
                printf("Invalid argument(s)\n");
                linenoiseFree(line);
                continue;
            } else if (argc > 0) {
                if (strcasecmp(argv[0],"quit") == 0 ||
                    strcasecmp(argv[0],"exit") == 0)
                {
                    exit(0);
                } else if (argv[0][0] == ':') {
                    cliSetPreferences(argv,argc,1);
                    continue;
                } else if (strcasecmp(argv[0],"restart") == 0) {
                    if (config.eval) {
                        config.eval_ldb = 1;
                        config.output = OUTPUT_RAW;
                        return; /* Return to evalMode to restart the session. */
                    } else {
                        printf("Use 'restart' only in Lua debugging mode.");
                    }
                } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
                    sdsfree(config.hostip);
                    config.hostip = sdsnew(argv[1]);
                    config.hostport = atoi(argv[2]);
                    cliRefreshPrompt();
                    cliConnect(1);
                } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
                    linenoiseClearScreen();
                } else {
                    long long start_time = mstime(), elapsed;
                    int repeat, skipargs = 0;
                    char *endptr;

                    repeat = strtol(argv[0], &endptr, 10);
                    if (argc > 1 && *endptr == '\0' && repeat) {
                        skipargs = 1;
                    } else {
                        repeat = 1;
                    }

                    issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);

                    /* If our debugging session ended, show the EVAL final
                     * reply. */
                    if (config.eval_ldb_end) {
                        config.eval_ldb_end = 0;
                        cliReadReply(0);
                        printf("\n(Lua debugging session ended%s)\n\n",
                            config.eval_ldb_sync ? "" :
                            " -- dataset changes rolled back");
                    }

                    elapsed = mstime()-start_time;
                    if (elapsed >= 500 &&
                        config.output == OUTPUT_STANDARD)
                    {
                        printf("(%.2fs)\n",(double)elapsed/1000);
                    }
                }
            }
            /* Free the argument vector */
            sdsfreesplitres(argv,argc);
        }
        /* linenoise() returns malloc-ed lines like readline() */
        linenoiseFree(line);
    }
    exit(0);
}

可以看到 linenoise 方法的调用,通过其中的 prompt 和 not connected> 可以判断出,这里向终端中输出了提示符,同时会调用 fgets 从标准输入中读取字符串. linenoise 是一款优秀的命令行编辑库,被广泛的运用在各种DB上.细节不展开。看下主要流程。

贴一下整理的调用关系,图片会更清晰:

 main-->repl()-->linenoise( issueCommandRepeat  )-->  cliSendCommand()

-->redisAppendCommandArgv(hiredis.c)-->redisFormatCommandArgv()
                                                                  -->__redisAppendCommand()
 -->cliReadReply(redis-cli.c)--->redisGetReply(hiredis.c)-->redisGetReply()-->redisBufferWrite(c->fd)

cliSendCommand方法会判断命令是否为特殊命令,如:help\shutdown等。客户端会根据以上命令设置对应的输出格式以及客户端的模式,因为这里我们是普通写入,所以不会涉及到以上的情况。

cliSendCommand方法会调用redisAppendCommandArgv方法,redisAppendCommandArgv方法会调用redisFormatCommandArgv和__redisAppendCommand方法。redisFormatCommandArgv方法用于将客户端输入的内容格式化成redis协议:

Redis 客户端与 Redis 服务进行通讯时,会使用名为 RESP(REdis Serialization Protocol) 的协议,它的使用非常简单,并且可以序列化多种数据类型包括整数、字符串以及数组等。

对于 RESP 协议的详细介绍可以看官方文档中的 Redis Protocol specification,在这里对这个协议进行简单的介绍。

在将不同的数据类型序列化时,会使用第一个 byte 来表示当前数据的数据类型,以便在客户端或服务器在处理时能恢复原来的数据格式。

     RESP “数据格式”的第一个字节用来表示数据类型,然后逻辑上属于不同部分的内容通过 CRLF(\r\n)分隔。   redisFormatCommandArgv的源码在hiredis.c

/* Format a command according to the Redis protocol. This function takes the
 * number of arguments, an array with arguments and an array with their
 * lengths. If the latter is set to NULL, strlen will be used to compute the
 * argument lengths.
 */
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
    char *cmd = NULL; /* final command */
    int pos; /* position in final command */
    size_t len;
    int totlen, j;

    /* Calculate number of bytes needed for the command */
    totlen = 1+intlen(argc)+2;
    for (j = 0; j < argc; j++) {
        len = argvlen ? argvlen[j] : strlen(argv[j]);
        totlen += bulklen(len);
    }

    /* Build the command at protocol level */
    cmd = malloc(totlen+1);
    if (cmd == NULL)
        return -1;

    pos = sprintf(cmd,"*%d\r\n",argc);
    for (j = 0; j < argc; j++) {
        len = argvlen ? argvlen[j] : strlen(argv[j]);
        pos += sprintf(cmd+pos,"$%zu\r\n",len);
        memcpy(cmd+pos,argv[j],len);
        pos += len;
        cmd[pos++] = '\r';
        cmd[pos++] = '\n';
    }
    assert(pos == totlen);
    cmd[pos] = '\0';

    *target = cmd;
    return totlen;
}

       好了回到主流程,接着客户端进入下一个流程,将outbuf内容写入到套接字描述符上并传输到服务器端。

   进入redisGetReply方法,该方法下主要有redisGetReplyFromReader和redisBufferWrite 方法,redisGetReplyFromReader主要用于读取挂起的回复,redisBufferWrite 方法用于将当前outbuf中的内容写入到套接字描述符中,并传输内容。源码如下:

int redisBufferWrite(redisContext *c, int *done) {
    int nwritten;

    /* Return early when the context has seen an error. */
    if (c->err)
        return REDIS_ERR;

    if (sdslen(c->obuf) > 0) {
        nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
        if (nwritten == -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 (nwritten > 0) {
            if (nwritten == (signed)sdslen(c->obuf)) {
                sdsfree(c->obuf);
                c->obuf = sdsempty();
            } else {
                sdsrange(c->obuf,nwritten,-1);
            }
        }
    }
    if (done != NULL) *done = (sdslen(c->obuf) == 0);
    return REDIS_OK;
}

此时客户端等待服务器端接收写入。

五、server 端接收写入、执行及返回结果

  服务器端依然在进行事件循环,在客户端发来内容的时候触发,对应的文件读取事件,readQueryFromClient。

后续的server的处理参数processInputBuffer,调用processCommand参见https://blog.csdn.net/bohu83/article/details/85009255
server返回结果:sendReplyToClient,将outbuf内容写入到套接字描述符并传输到客户端,主要方法如下:
1 、nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
2、aeDeleteFileEvent 用于删除 文件写事件

六 、Client收到返回结果

其实获取服务器回复和上文中的发送命令过程基本上差不多,也是redisGetReply获取服务器响应。调用关系如下:

cliReadReply(redis-cli.c)-->redisGetReply(hiredis.c)-->redisGetReply()--> redisBufferRead(hiredis.c)
                                                                      --> redisGetReplyFromReader(hiredis)-->redisReaderGetReply()
                         -->cliFormatReplyRaw()
                         -->fwrite(out,sdslen(out),1,stdout);

static int cliReadReply(int output_raw_strings) {
    void *_reply;
    redisReply *reply;
    sds out = NULL;
    int output = 1;

    if (redisGetReply(context,&_reply) != REDIS_OK) {
        if (config.shutdown) {
            redisFree(context);
            context = NULL;
            return REDIS_OK;
        }
        if (config.interactive) {
            /* Filter cases where we should reconnect */
            if (context->err == REDIS_ERR_IO &&
                (errno == ECONNRESET || errno == EPIPE))
                return REDIS_ERR;
            if (context->err == REDIS_ERR_EOF)
                return REDIS_ERR;
        }
        cliPrintContextError();
        exit(1);
        return REDIS_ERR; /* avoid compiler warning */
    }

    reply = (redisReply*)_reply;

    config.last_cmd_type = reply->type;

    /* Check if we need to connect to a different node and reissue the
     * request. */
    if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
        (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
    {
        char *p = reply->str, *s;
        int slot;

        output = 0;
        /* Comments show the position of the pointer as:
         *
         * [S] for pointer 's'
         * [P] for pointer 'p'
         */
        s = strchr(p,' ');      /* MOVED[S]3999 127.0.0.1:6381 */
        p = strchr(s+1,' ');    /* MOVED[S]3999[P]127.0.0.1:6381 */
        *p = '\0';
        slot = atoi(s+1);
        s = strrchr(p+1,':');    /* MOVED 3999[P]127.0.0.1[S]6381 */
        *s = '\0';
        sdsfree(config.hostip);
        config.hostip = sdsnew(p+1);
        config.hostport = atoi(s+1);
        if (config.interactive)
            printf("-> Redirected to slot [%d] located at %s:%d\n",
                slot, config.hostip, config.hostport);
        config.cluster_reissue_command = 1;
        cliRefreshPrompt();
    }

    if (output) {
        if (output_raw_strings) {
            out = cliFormatReplyRaw(reply);
        } else {
            if (config.output == OUTPUT_RAW) {
                out = cliFormatReplyRaw(reply);
                out = sdscat(out,"\n");
            } else if (config.output == OUTPUT_STANDARD) {
                out = cliFormatReplyTTY(reply,"");
            } else if (config.output == OUTPUT_CSV) {
                out = cliFormatReplyCSV(reply);
                out = sdscat(out,"\n");
            }
        }
        fwrite(out,sdslen(out),1,stdout);
        sdsfree(out);
    }
    freeReplyObject(reply);
    return REDIS_OK;
}

函数主要流程是 调用redisGetReply 获取reply ,结构是redisReply,源码在hiredis.h

/* 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 */
     /*字符串值的长度*/
    int 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;
  • REDIS_REPLY_STRING == 1:返回值是字符串,字符串储存在redis->str当中,字符串长度为redis->len。
  • REDIS_REPLY_ARRAY == 2:返回值是数组,数组大小存在redis->elements里面,数组值存储在redis->element[i]里面。数组里面存储的是指向redisReply的指针,数组里面的返回值可以通过redis->element[i]->str来访问,数组的结果里全是type==REDIS_REPLY_STRING的redisReply对象指针。
  • REDIS_REPLY_INTEGER == 3:返回值为整数 long long。
  • REDIS_REPLY_NIL==4:返回值为空表示执行结果为空。
  • REDIS_REPLY_STATUS ==5:返回命令执行的状态,比如set foo bar 返回的状态为OK,存储在str当中 reply->str == "OK"。
  • REDIS_REPLY_ERROR ==6 :命令执行错误,错误信息存放在 reply->str当中。 

并将数据交给回复解析器处理,也就是cliFormatReplyRaw,该方法将回复内容格式化。最终通过

fwrite(out,sdslen(out),1,stdout);

方法返回给客户端并打印展示给用户。

再看看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;
}

在 redisBufferWrite 成功发送命令并返回之后,就会开始等待服务端的回复,总共分为两个部分,一是使用 redisBufferRead 从服务端读取原始格式的回复(符合 RESP 协议)该方法主要用于从socket中读取数据。主要方法如下:

nread = read(c->fd,buf,sizeof(buf));

最后的 redisGetReplyFromReader 方法会从 redisContext 中取出 reader,然后反序列化 RESP 对象,最后打印出来。

限于篇幅,很多函数没有贴出来,关于解析的相关结构体redisReader,也没整理。感兴趣的可以自己看看源码。

总结:

 对于redis的常规的命令get\set,理解它执行一条命令执行的整个流程,对于熟悉它整个运行流程较为有利。

当然Linux底层的掌握看起来更有助于理解。

 

参考:

https://www.jianshu.com/p/0944c16b2353

https://www.jianshu.com/p/9ed98c7cbe6b

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值