redis-cli中有两个重要的结构体,一个是redisContext与一个是config
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 fd
int flags;
char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */
} redisContext;
redisContext跟redis-cli与redis server的tcp连接相关配置
config是redis-cli运行的配置文件,有初始化默认配置,也可以通过运行的时候传递参数修改配置
static struct config {
aeEventLoop *el;
const char *hostip;
int hostport;
const char *hostsocket;
int numclients;
int liveclients;
int requests;
int requests_issued;
int requests_finished;
int keysize;
int datasize;
int randomkeys;
int randomkeys_keyspacelen;
int keepalive;
int pipeline;
int showerrors;
long long start;
long long totlatency;
long long *latency;
const char *title;
list *clients;
int quiet;
int csv;
int loop;
int idlemode;
int dbnum;
sds dbnumstr;
char *tests;
char *auth;
} config;
在main函数中,首先对config结构体中的配置进行初始化。
其中,config结构体中sds是redis自定义的字符串数据结构,定义为typedef char* sds
配置spectrum_palette和spectrum_palette size两个全局变量怀疑与cli命令行中的提示颜色有关
使用isatty函数判断是否连接的是终端,从而配置config.output为OUTPUT_RAW或者OUTPUT_STANDARD
使用parseOption(argc,argv)通过运行程序携带参数对config进行进一步配置
config配置完后开始根据配置配模式
1. latency mode
2. latency distribution mode
3. slave mode
4. get RDB mode
5. Pipe mode
6. stat mode
7. scan mode
8. LRU mode
9. Intrinsic latency mode
如果没有任何附加命令,则运行默认的交互模式(interactive mode)
/* Ignore SIGPIPE in interactive mode to force a reconnect */
signal(SIGPIPE, SIG_IGN);//忽略sigpipe,如果尝试连接两次已经关闭的socket会导致sigpipe,告诉进程这个连接已经断开了,不要再写了,一般客户端默认退出,但是此处忽略SIGPIPE信号
/* 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(0)连接redis server服务器
cliConnect(0)中,使用anetKeepAlive运用TCP_KEEPALIVE选项防止超时
连接上redis server之后,调用repl(),开始读取stdin运行redis命令
redpl中,首先设置是否接收多命令形式,然后初始化设置处理commond 命令行的回调函数
通过循环调用linenoise函数,得到输入的commond命令
linenoise有三种模式noTTY,unSuportedTerm,raw模式,一般使用的是raw模式
在linenoiseraw模式中,使用linenoiseEdit函数对每次输入进行处理,从而达到较为友好的交互
static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
{
struct linenoiseState l;
/* Populate the linenoise state that we pass to functions implementing
* specific editing functionalities. */
l.ifd = stdin_fd;
l.ofd = stdout_fd;
l.buf = buf;
l.buflen = buflen;
l.prompt = prompt;
l.plen = strlen(prompt);
l.oldpos = l.pos = 0;
l.len = 0;
l.cols = getColumns(stdin_fd, stdout_fd);
l.maxrows = 0;
l.history_index = 0;
/* Buffer starts empty. */
l.buf[0] = '\0';
l.buflen--; /* Make sure there is always space for the nulterm */
/* The latest history entry is always our current buffer, that
* initially is just an empty string. */
linenoiseHistoryAdd("");
if (write(l.ofd,prompt,l.plen) == -1) return -1;
while(1) {
char c;
int nread;
char seq[3];
nread = read(l.ifd,&c,1);
if (nread <= 0) return l.len;
/* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd. Otherwise it will return the
* character that should be handled next. */
if (c == 9 && completionCallback != NULL) {
c = completeLine(&l);
/* Return on errors */
if (c < 0) return l.len;
/* Read next character when 0 */
if (c == 0) continue;
}
switch(c) {
case ENTER: /* enter */
history_len--;
free(history[history_len]);
if (mlmode) linenoiseEditMoveEnd(&l);
if (hintsCallback) {
/* Force a refresh without hints to leave the previous
* line as the user typed it after a newline. */
linenoiseHintsCallback *hc = hintsCallback;
hintsCallback = NULL;
refreshLine(&l);
hintsCallback = hc;
}
return (int)l.len;
case CTRL_C: /* ctrl-c */
errno = EAGAIN;
return -1;
case BACKSPACE: /* backspace */
case 8: /* ctrl-h */
linenoiseEditBackspace(&l);
break;
case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
line is empty, act as end-of-file. */
if (l.len > 0) {
linenoiseEditDelete(&l);
} else {
history_len--;
free(history[history_len]);
return -1;
}
break;
case CTRL_T: /* ctrl-t, swaps current character with previous. */
if (l.pos > 0 && l.pos < l.len) {
int aux = buf[l.pos-1];
buf[l.pos-1] = buf[l.pos];
buf[l.pos] = aux;
if (l.pos != l.len-1) l.pos++;
refreshLine(&l);
}
break;
case CTRL_B: /* ctrl-b */
linenoiseEditMoveLeft(&l);
break;
case CTRL_F: /* ctrl-f */
linenoiseEditMoveRight(&l);
break;
case CTRL_P: /* ctrl-p */
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
break;
case CTRL_N: /* ctrl-n */
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
break;
case ESC: /* escape sequence */
/* Read the next two bytes representing the escape sequence.
* Use two calls to handle slow terminals returning the two
* chars at different times. */
if (read(l.ifd,seq,1) == -1) break;
if (read(l.ifd,seq+1,1) == -1) break;
/* ESC [ sequences. */
if (seq[0] == '[') {
if (seq[1] >= '0' && seq[1] <= '9') {
/* Extended escape, read additional byte. */
if (read(l.ifd,seq+2,1) == -1) break;
if (seq[2] == '~') {
switch(seq[1]) {
case '3': /* Delete key. */
linenoiseEditDelete(&l);
break;
}
}
} else {
switch(seq[1]) {
case 'A': /* Up */
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
break;
case 'B': /* Down */
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
break;
case 'C': /* Right */
linenoiseEditMoveRight(&l);
break;
case 'D': /* Left */
linenoiseEditMoveLeft(&l);
break;
case 'H': /* Home */
linenoiseEditMoveHome(&l);
break;
case 'F': /* End*/
linenoiseEditMoveEnd(&l);
break;
}
}
}
/* ESC O sequences. */
else if (seq[0] == 'O') {
switch(seq[1]) {
case 'H': /* Home */
linenoiseEditMoveHome(&l);
break;
case 'F': /* End*/
linenoiseEditMoveEnd(&l);
break;
}
}
break;
default:
if (linenoiseEditInsert(&l,c)) return -1;
break;
case CTRL_U: /* Ctrl+u, delete the whole line. */
buf[0] = '\0';
l.pos = l.len = 0;
refreshLine(&l);
break;
case CTRL_K: /* Ctrl+k, delete from current to end of line. */
buf[l.pos] = '\0';
l.len = l.pos;
refreshLine(&l);
break;
case CTRL_A: /* Ctrl+a, go to the start of the line */
linenoiseEditMoveHome(&l);
break;
case CTRL_E: /* ctrl+e, go to the end of the line */
linenoiseEditMoveEnd(&l);
break;
case CTRL_L: /* ctrl+l, clear screen */
linenoiseClearScreen();
refreshLine(&l);
break;
case CTRL_W: /* ctrl+w, delete previous word */
linenoiseEditDeletePrevWord(&l);
break;
}
}
return l.len;
}
在得到cli命令字符串后,先对把这些命令存到历史文件里面,需要对该命令进行语义的分析,包括quit,exit,restart,connect,clear等等,然后进行相对应的操作。
如果是与redis server相关的命令,则通过issueCommandRepeat函数发送和接收数据
static int issueCommandRepeat(int argc, char **argv, long repeat) {
while (1) {
config.cluster_reissue_command = 0;
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
cliConnect(1);
/* If we still cannot send the command print error.
* We'll try to reconnect the next time. */
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
cliPrintContextError();
return REDIS_ERR;
}
}
/* Issue the command again if we got redirected in cluster mode */
if (config.cluster_mode && config.cluster_reissue_command) {
cliConnect(1);
} else {
break;
}
}
return REDIS_OK;
}