redis源码之客户端请求处理(二)
简介
redis的设计是事件驱动模型,对于客户端连接的处理主要是对两个事件的处理:
- 服务端的监听端口注册了可读事件处理函数acceptTcpHandler,用于处理客户端请求建立tcp连接;该函数会接收客户端请求并生成一个网络连接句柄,然后封装成一个client对象。
- 在服务端接收客户端请求命令后,服务端通过epoll系统调用设置这个tcp连接句柄的可读事件处理函数为readQueryFromClient;readQueryFromClient会解析客户端的命令,并调用命令对应的处理函数进行处理。
tcp连接的封装
connection对象
// 将网络连接句柄封装成connection对象
struct connection {
ConnectionType *type;
ConnectionState state;
short int flags;
short int refs;
int last_errno;
void *private_data;
ConnectionCallbackFunc conn_handler;
ConnectionCallbackFunc write_handler;
ConnectionCallbackFunc read_handler;
// 网络连接句柄
int fd;
};
// 使用网络句柄fd封装一个conn对象
connection *connCreateAcceptedSocket(int fd) {
connection *conn = connCreateSocket();
conn->fd = fd;
conn->state = CONN_STATE_ACCEPTING;
return conn;
}
// 客户端connetion对象的type接口的实现
ConnectionType CT_Socket = {
.ae_handler = connSocketEventHandler,
.close = connSocketClose,
.write = connSocketWrite,
.read = connSocketRead,
.accept = connSocketAccept,
.connect = connSocketConnect,
.set_write_handler = connSocketSetWriteHandler,
.set_read_handler = connSocketSetReadHandler,
.get_last_error = connSocketGetLastError,
.blocking_connect = connSocketBlockingConnect,
.sync_write = connSocketSyncWrite,
.sync_read = connSocketSyncRead,
.sync_readline = connSocketSyncReadLine,
.get_type = connSocketGetType
};
// 设置conn->fd的可读事件处理函数为func
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
if (func == conn->read_handler) return C_OK;
conn->read_handler = func;
if (!conn->read_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
else
if (aeCreateFileEvent(server.el,conn->fd,
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
client对象
// 将connection封装成client对象
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
connection *conn;
int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */
// 存放从客户端读取的数据
sds querybuf; /* Buffer we use to accumulate client queries. */
// 下一个要处理的命令的第一个字符
size_t qb_pos; /* The position we have read in querybuf. */
sds pending_querybuf; /* If this client is flagged as master, this buffer
represents the yet not applied portion of the
replication stream that we are receiving from
the master. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
// argv数组长度
int argc; /* Num of arguments of current command. */
// 待处理的命令
robj **argv; /* Arguments of current command. */
...
}
// 使用conn封装一个client对象, 设置网络连接句柄的可读事件处理函数伟readQueryFromClient
client *createClient(connection *conn) {
client *c = zmalloc(sizeof(client));
/* passing NULL as conn it is possible to create a non connected client.
* This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
if (conn) {
connNonBlock(conn);
connEnableTcpNoDelay(conn);
if (server.tcpkeepalive)
connKeepAlive(conn,server.tcpkeepalive);
connSetReadHandler(conn, readQueryFromClient);
connSetPrivateData(conn, c);
}
selectDb(c,0);
...
}
客户端与服务器的交互
服务端监听端口的事件处理函数注册
int main(int argc, char **argv) server.c
void initServer(void) server.c
// 设置tcp服务监听端口的读事件处理函数为acceptTcpHandler
int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler)
// 接受客户端的请求,并且将网络连接进行多层封装,最后封装成client对象
// 并且设置客户端网络连接可读事件处理函数为readQueryFromClient
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask)
- 服务器监听端口的可读事件处理函数为acceptTcpHandler,所以有新的客户端的连接请求的时候会调用改函数处理
客户端网络连接可读事件处理函数注册
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask)
connection *connCreateAcceptedSocket(int fd)
// 创建一个空的connection对象,connetion type为CT_Socket
// type为CT_Socket,这里定义了socket不同事件的处理函数
connection *connCreateSocket()
static void acceptCommonHandler(connection *conn, int flags, char *ip)
// 使用conn封装一个client对象,
// 设置网络连接句柄的可读事件处理函数为readQueryFromClient
client *createClient(connection *conn)
// 客户端网络连接句柄可读事件的处理函数
void readQueryFromClient(connection *conn)
- 客户端网络连接句柄的可读事件处理函数为readQueryFromClient
客户端数据处理
void readQueryFromClient(connection *conn)
static int connSocketRead(connection *conn, void *buf, size_t buf_len)
void processInputBuffer(client *c)
// 从c->querybuf取一行数据,放入c->argv
int processInlineBuffer(client *c)
// 处理客户端命令
int processCommandAndResetClient(client *c)
// 进行一些检查后,执行命令
int processCommand(client *c)
// 调用命令处理函数c->cmd->proc
void call(client *c, int flags)
c->cmd->proc(c)
命令和对应的处理函函数
在服务器进程初始化的时候,会调用populateCommandTable生成一个命令和处理函数的映射字典server.commands。处理客户端的命令的时候,就会在server.commands中找到对应的命令对象redisCommand,调用其proc处理客户端的命令。比如set命令的proc是setCommand。而命令行的参数已经解析放入c->argv,命令参数个数为c->argc。
// 命令处理函数,如set命令的处理函数是setCommand
typedef void redisCommandProc(client *c);
// 命令
struct redisCommand {
// 命令名称,如set
char *name;
// 命令对应的处理函数, 如setCommand
redisCommandProc *proc;
int arity;
// 字符串格式的标志
char *sflags; /* Flags as string representation, one char per flag. */
// 位格式的标志
uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */
redisGetKeysProc *getkeys_proc;
/* What keys should be loaded in background when calling this command? */
int firstkey; /* The first argument that's a key (0 = no keys) */
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */
// 命令调用次数计数
long long microseconds, calls, rejected_calls, failed_calls;
int id; /* Command ID. This is a progressive ID starting from 0 that
is assigned at runtime, and is used in order to check
ACLs. A connection is able to execute a given command if
the user associated to the connection has this command
bit set in the bitmap of allowed commands. */
};
// 命令名称和对应的处理函数
struct redisCommand redisCommandTable[] = {
...
// set命令的处理函数是setCommand
{"set",setCommand,-3,
"write use-memory @string",
0,NULL,1,1,1,0,0,0},
...
}
// 将redisCommandTable中的命令插入到server.command列表中
// 后面可以快速根据命令名称查找到命令对象redisCommand
// 比如根据客户端发送的"set"命令,查找到"set"命令对应的redisCommand, 然后调用redisCommand.proc处理命令
/* Populates the Redis Command Table starting from the hard coded list
* we have on top of server.c file. */
void populateCommandTable(void) {
int j;
int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;
int retval1, retval2;
/* Translate the command string flags description into an actual
* set of flags. */
// 根据sflags设置flags
if (populateCommandTableParseFlags(c,c->sflags) == C_ERR)
serverPanic("Unsupported command flag");
c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
// 将命令放入字典server.commands
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
/* Populate an additional dictionary that will be unaffected
* by rename-command statements in redis.conf. */
retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
}
}

浙公网安备 33010602011771号