redis客户端连接到服务器的步骤

和大多数客户端连接到服务器一样,redis-cli连接到服务器也主要分为两个阶段,请求连接阶段和数据传送阶段。具体来讲redis-cli做的事情有:

1、以socket方式建立连接;

2,选择相应的数据库;

3,对客户端发送的命令进行编码;

4,发送客户端编码的数据(write);

5,接收服务器回应的数据(read);

6,解析接收的数据。

以下根据源码对客户端所做的事情进行分析。

/* 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();
}
以上代码为redis-cli主函数中关于交互模式的部分,主要由cliConnect和repl构成,其中前者负责连接服务器,后者负责进行数据传输。

建立连接
关于客户端与服务器建立连接,我们知道主要的步骤分为、

1,创建socket(有一套固定的模式);

2,根据设定的ip及端口号,与服务器进行connet;

好的,这本来就是传统的做法,咱们看看redis它怎么做了呢?

它首先出定义了一个redis上下文结构,包含一次请求及回应的数据信息以及状态信息如下:

typedef struct redisContext {
int err;
char errstr[128];
int fd;
int flags;
char *obuf;
redisReader *reader;
enum redisConnectionType connection_type;
struct timeval *timeout;
struct {
char *host;
char *source_addr;
int port;
} tcp;

struct {
char *path;
} unix_sock;
} redisContext;
err、errstr为接收数据不正常时定义的变量,fd为客户端创建的sockfd,obuf为客户端的编码命令,reader为服务器返回的数据。并且要通过cliConnect进行初始化,定义tcp,fd等信息。

先看看cliConnect的操作

* 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);
}
...

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;
}
flags用于表示客户端在已经连接到了服务器的情况下,是否还能在连接,0表示不允许连接,1表示允许连接(在改变客户端登录的服务器时会用到)。

redisConnect定义了context的ip,port;调用redisContextInit初始化字符串,调用redisContextConnectTcp(c,ip,port,NULL)完成了socket连接。

由于已经连接完成,cliSelect调用命令

reply = redisCommand(context,"SELECT %d",config.dbnum);
选0号数据库(默认)。

数据传输
在repl()中,使用了linenoise工具处理输入的行,字符串分割部分采用了cliSplitArgs,参数处理部分采用issueCommandRepeat。它调用redisAppendCommandArgv将参数编码,传递给context的obu;调用cliReadReply负责解码客户端接收到的数据;调用redisGetReply(在cliReadReply中)负责底层I/O数据的传输。

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,分别向I/Obuf中写入和读取数据。redisGetReplyFromReader负责解码接收数据。

总结:
1,redis-cli在基本的客户端编程的基础上,增加了Context定义,可以知道数据的类型,数据的好坏。

2,增加了编码解码功能,一般编码解码功能可以使数据更安全,不知是不是因为这样才进行编码解码。

3,具体的实现部分还是需要进一步学习。

 
--------------------- 

posted on 2019-06-02 01:47  激流勇进1  阅读(3525)  评论(0编辑  收藏  举报