redis-cli的實現(xiàn)原理

首先從源碼中找入口

redis源碼:src/redis-cli.c中找到main函數(shù),main函數(shù)中核心的處理就是以下部分

    /* 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();
    }
  • cliConnect
    主要是與服務端建立連接沥阳,每一個連接都會創(chuàng)建一個redisContext結(jié)構(gòu)來保存
  • repl
    repl實現(xiàn)了發(fā)送命令并輸出Server返回結(jié)果的主要邏輯

RedisContext

redisContext結(jié)構(gòu)如下,重要的字段都進行了注釋

typedef struct redisContext {
    int err; /* Error flags, 0 when there is no error */
    char errstr[128]; /* String representation of error when applicable */
    int fd; // socket句柄自点,用戶連接redis server
    int flags;
    char *obuf; /* Write buffer */ //主要存儲發(fā)送的命令桐罕,resp協(xié)議封裝后的sds
    redisReader *reader; /* Protocol reader */ // 存儲server返回的數(shù)據(jù)

    enum redisConnectionType connection_type;
    struct timeval *timeout;

    struct {
        char *host;
        char *source_addr;
        int port;
    } tcp;

    struct {
        char *path;
    } unix_sock;

} redisContext;

repl是做什么的

repl其實質(zhì)就是在不停的重復解析用戶輸入的命令和redis server返回的參數(shù)。repl中桂敛,實現(xiàn)這個核心操作的便是issueCommandRepeat方法功炮。

issueCommandRepeat方法我們直觀來想,需要做3步操作

  • 從標準輸入獲取用戶輸入的命令和參數(shù)术唬,并按照resp協(xié)議封裝
  • 將封裝后的數(shù)據(jù)發(fā)送至服務器
  • 讀取從服務器返回的結(jié)果并解析輸出

cli如何封裝輸入的命令和參數(shù)

通過對issueCommandRepeat方法的分析薪伏,極其對它里邊調(diào)用關(guān)系的梳理,發(fā)現(xiàn)是redisAppendCommandArgv處理命令并將命令寫入context的obuf中粗仓,redisAppendCommandArgv的調(diào)用層級和源碼如下

issueCommandRepeat
    cliSendCommand
        redisAppendCommandArgv
            redisFormatSdsCommandArgv
                __redisAppendCommand 
源碼:
    sds cmd;
    int len;
    
    //redisFormatSdsCommandArgv是將命令極其跟隨的參數(shù)嫁怀,使用resp協(xié)議封裝后,存到一個sds結(jié)構(gòu)中借浊。
    len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
    if (len == -1) {
        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
        return REDIS_ERR;
    }

    //__redisAppendCommand是將sds保存的resp協(xié)議的數(shù)據(jù)存到redisContext中的obuf中
    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
        sdsfree(cmd);
        return REDIS_ERR;
    }

    // 釋放sds結(jié)構(gòu)申請的內(nèi)存
    sdsfree(cmd);
    return REDIS_OK;

向服務器發(fā)送命令

有了封裝好的數(shù)據(jù)塘淑,下一步就是可以向服務器發(fā)送命令了,還是issueCommandRepeat方法蚂斤,看下述代碼

while(repeat-- > 0) {
        redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
        while (config.monitor_mode) {
            if (cliReadReply(output_raw) != REDIS_OK) exit(1);
            fflush(stdout);
        }

        if (config.pubsub_mode) {
            if (config.output != OUTPUT_RAW)
                printf("Reading messages... (press Ctrl-C to quit)\n");
            while (1) {
                if (cliReadReply(output_raw) != REDIS_OK) exit(1);
            }
        }

        if (config.slave_mode) {
            printf("Entering replica output mode...  (press Ctrl-C to quit)\n");
            slaveMode();
            config.slave_mode = 0;
            zfree(argvlen);
            return REDIS_ERR;  /* Error = slaveMode lost connection to master */
        }

        if (cliReadReply(output_raw) != REDIS_OK) {
            zfree(argvlen);
            return REDIS_ERR;
        } else {
            /* Store database number when SELECT was successfully executed. */
            if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
                config.dbnum = atoi(argv[1]);
                cliRefreshPrompt();
            } else if (!strcasecmp(command,"auth") && argc == 2) {
                cliSelect();
            }
        }
        if (config.interval) usleep(config.interval);
        fflush(stdout); /* Make it grep friendly */
    }      

我們知道redisAppendCommandArgv只是組裝了命令存捺,并沒有發(fā)送,cliReadReply看樣子是將結(jié)果讀取曙蒸,最后fflush是將結(jié)果輸出到標準輸出捌治。那么發(fā)送命令只可能藏在cliReadReply中,繼續(xù)分析cliReadyReply

cliReadyReply
    redisGetReply

在redisGetReply中發(fā)現(xiàn)特別隱藏的redisBufferWrite纽窟,這個實際是發(fā)送了請求肖油,我們看具體代碼

    if (sdslen(c->obuf) > 0) {
        // 發(fā)送命令
        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) {
            // 發(fā)送成功,清理obuf
            if (nwritten == (signed)sdslen(c->obuf)) {
                sdsfree(c->obuf);
                c->obuf = sdsempty();
            } else {
                sdsrange(c->obuf,nwritten,-1);
            }
        }
    }

讀取服務器返回的結(jié)果

繼續(xù)在redisGetReply讀代碼臂港,能夠看到redisBufferRead是獲取服務器返回的數(shù)據(jù)的方法森枪。

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;
        
    // 從fd讀取數(shù)據(jù)
    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;
}

從代碼可以看到如果讀取成功會調(diào)用redisReaderFeed將buf內(nèi)容寫入到redisContext中的reader里

redisGetReply中,又調(diào)用了redisGetReplyFromReader趋艘,redisReaderGetReply將返回的數(shù)據(jù)通過resp協(xié)議解析為字符串

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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疲恢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓷胧,更是在濱河造成了極大的恐慌显拳,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搓萧,死亡現(xiàn)場離奇詭異杂数,居然都是意外死亡宛畦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門揍移,熙熙樓的掌柜王于貴愁眉苦臉地迎上來次和,“玉大人,你說我怎么就攤上這事那伐√な” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵罕邀,是天一觀的道長畅形。 經(jīng)常有香客問我,道長诉探,這世上最難降的妖魔是什么日熬? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮肾胯,結(jié)果婚禮上竖席,老公的妹妹穿的比我還像新娘。我一直安慰自己敬肚,他們只是感情好毕荐,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帘皿,像睡著了一般东跪。 火紅的嫁衣襯著肌膚如雪畸陡。 梳的紋絲不亂的頭發(fā)上鹰溜,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音丁恭,去河邊找鬼曹动。 笑死,一個胖子當著我的面吹牛牲览,可吹牛的內(nèi)容都是我干的墓陈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼第献,長吁一口氣:“原來是場噩夢啊……” “哼贡必!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起庸毫,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤仔拟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后飒赃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體利花,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡科侈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了炒事。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片臀栈。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挠乳,靈堂內(nèi)的尸體忽然破棺而出权薯,到底是詐尸還是另有隱情,我是刑警寧澤睡扬,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布崭闲,位于F島的核電站,受9級特大地震影響威蕉,放射性物質(zhì)發(fā)生泄漏刁俭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一韧涨、第九天 我趴在偏房一處隱蔽的房頂上張望牍戚。 院中可真熱鬧,春花似錦虑粥、人聲如沸如孝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽第晰。三九已至,卻和暖如春彬祖,著一層夾襖步出監(jiān)牢的瞬間茁瘦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工储笑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甜熔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓突倍,卻偏偏與公主長得像腔稀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子羽历,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容