公众号:嵌入式不难
本文仅供参考学习,如有错误之处,欢迎留言指正。
redis测试
进入redis源码下src目录,准备测试文件
如果已经编译过redis了,那么在src目录下会存在redis-server和redis-cli可执行文件,如下图
如果没有以上的文件,则需要首先在redis目录下执行make
命令。
这两个文件一个是服务端 redis-server ,一个客户端 redis-cli。测试的时候两个都需要启动, redis-server 作为服务端会响应 redis-cli客户端的请求。最后在 redis-cli的终端上呈现出 redis-server 的响应结果。
进入src目录启动服务端,命令:./redis-server
进入src目录启动客户端
启动客户端携带参数。例:启动帮助手册,命令:./redis-cli --help
启动客户端未携带参数。例:正常执行redis的功能,命令:./redis-cli
redis-cli代码执行流程
main 入口
redis-cli的 main 入口位于 redis_cli.c 中。 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;
Step1: main —> parseOptions 函数解析命令参数并保存至 config 中
main 中部分代码粘贴如下:
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;
parseEnv();
parseOptions 函数粘贴部分代码如下,可以看到这里在解析命令行输入的参数,例如:-h,--help,-r,-x,-p,-s......
。将解析结果保存至全局变量 config 中
static int parseOptions(int argc, char **argv) {
int i;
for (i = 1; i < argc; i++) {
int lastarg = i==argc-1;
if (!strcmp(argv[i],"-h") && !lastarg) {
sdsfree(config.hostip);
config.hostip = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"-h") && lastarg) {
usage();
} else if (!strcmp(argv[i],"--help")) {
usage();
} else if (!strcmp(argv[i],"-x")) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-s") && !lastarg) {
config.hostsocket = argv[++i];
} else if (!strcmp(argv[i],"-r") && !lastarg) {
config.repeat = strtoll(argv[++i],NULL,10);
} else if (!strcmp(argv[i],"-i") && !lastarg) {
double seconds = atof(argv[++i]);
config.interval = seconds*1000000;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.dbnum = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-auth-warning")) {
config.no_auth_warning = 1;
} else if (!strcmp(argv[i],"-a") && !lastarg) {
config.auth = argv[++i];
} else if (!strcmp(argv[i],"-u") && !lastarg) {
parseRedisUri(argv[++i]);
} else if (!strcmp(argv[i],"--raw")) {
config.output = OUTPUT_RAW;
} else if (!strcmp(argv[i],"--no-raw")) {
config.output = OUTPUT_STANDARD;
} else if (!strcmp(argv[i],"--csv")) {
Step2:根据由 parseOptions 写入全局变量 config 的结果来判定是否进入某种特定模式,当进入某种特定模式后,执行完特定功能后,便执行exit
函数终止线程。粘贴部分代码如下
main 中部分代码粘贴如下:
/* 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(0, 0);
}
/* Find large keys */
if (config.memkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
findBigKeys(1, config.memkeys_samples);
}
/* Find hot keys */
if (config.hotkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
findHotKeys();
}
/* 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();
}
scanMode 接口粘贴如下:
static void scanMode(void) {
redisReply *reply;
unsigned long long cur = 0;
do {
if (config.pattern)
reply = redisCommand(context,"SCAN %llu MATCH %s",
cur,config.pattern);
else
reply = redisCommand(context,"SCAN %llu",cur);
if (reply == NULL) {
printf("I/O error\n");
exit(1);
} else if (reply->type == REDIS_REPLY_ERROR) {
printf("ERROR: %s\n", reply->str);
exit(1);
} else {
unsigned int j;
cur = strtoull(reply->element[0]->str,NULL,10);
for (j = 0; j < reply->element[1]->elements; j++)
printf("%s\n", reply->element[1]->element[j]->str);
}
freeReplyObject(reply);
} while(cur != 0);
exit(0);
}
Step3:在执行./redis-cli
时,会进入如下程序段,该程序段执行两个功能:1.连接服务器:cliConnect(0);
、2.接收终端输入并发送至服务器,并将服务器返回的信息输出至终端:repl();
main 中摘取代码片段如下
/* 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();
}
Step4: main —> cliConnect 函数解析。 cliConnect 的功能为建立服务器与当前客户端的连接。 cliConnect 的函数调用链为cliConnect
—>redisConnect(config.hostip,config.hostport)
—>redisContextConnectTcp
—>socket+bind+connect
。且 cliConnect 会调用cliAuth+cliSelect
向服务器发送AUTH和SELECT dbnum命令。粘贴部分代码如下
int cliConnect(int flags)
接口如下
/* Connect to the server. It is possible to pass certain flags to the function:
* CC_FORCE: The connection is performed even if there is already
* a connected socket.
* CC_QUIET: Don't print errors if connection fails. */
static int cliConnect(int flags) {
if (context == NULL || flags & CC_FORCE) {
if (context != NULL) {
redisFree(context);
}
if (config.hostsocket == NULL) {
context = redisConnect(config.hostip,config.hostport);
} else {
context = redisConnectUnix(config.hostsocket);
}
if (context->err) {
if (!(flags & CC_QUIET)) {
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;
if (cliSelect() != REDIS_OK)
return REDIS_ERR;
}
return REDIS_OK;
}
Step5: main —> repl 函数解析,我将 repl 函数的功能分为两部分,第一部分是处理键盘的键入,第二部分是将指令发送至服务器并处理。
第一部分:处理键盘的键入,这里使用到了第三方库linenoise
,其中linenoiseSetHintsCallback(hintsCallback);
回调的功能是每键入一个值都会进入此回调,利用此特性,可以提示用户接下来需要键入什么以满足命令格式,linenoiseSetFreeHintsCallback(freeHintsCallback);
回调的功能是每删除一个值都会进入此回调,linenoise
接口返回键入的完整字符串。
提示功能如下
repl 部分代码粘贴如下
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);
//keep in-memory history always regardless if history file can be determined
history = 1;
if (historyfile != NULL) {
linenoiseHistoryLoad(historyfile);
}
cliLoadPreferences();
}
cliRefreshPrompt();
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
long repeat = 1;
int skipargs = 0;
char *endptr = NULL;
第二部分:将用户的键入值发送至服务器,实现此功能的接口为issueCommandRepeat
,此函数的接口调用链为issueCommandRepeat
—>cliSendCommand//解析参数并命令
—>cliReadReply//读取回复,并根据需要输出至终端
—>redisGetReply//获取回复
—>redisBufferWrite//fd的写操作,即表示向服务器发送消息 & redisBufferRead//fd的读操作,即表示读取服务器的返回消息
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(CC_FORCE);
/* 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(CC_FORCE);
} else {
break;
}
}
return REDIS_OK;
}
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
接口代码如下
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;
}
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;
}