接下来,我们就跟随一下代码,或者说函数调用栈,来看下。Redis是怎么做的。
当前,我们先刨除一些业务上的代码,仅仅只看有关网络的代码。
// redis.c
int main(int argc, char **argv) {
// 初始化服务器
// 这里主要设置就是服务器的一些参数,可以不关心
initServerConfig();
// 将服务器设置为守护进程
if (server.daemonize) daemonize();
// 创建并初始化服务器数据结构
initServer();
...
}
// initServer
void initServer() {
// 初始化EventLoop
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
// 初始化大哈希表
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
// 打开 TCP 监听端口,用于等待客户端的命令请求
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(1);
// 为 TCP 连接关联连接应答(accept)处理器
// 用于接受并应答客户端的 connect() 调用
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
redisPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
}
简要说明下。
- 设置Reactor
- 完成socket, bink, listen
- 注册acceptTcpHandler,相当于为反应堆注入初始事件。那么一旦连接,反应堆就会开始运作。可以猜到,在注册acceptTcpHandler中,肯定有关于1. 新连接到来的处理。
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
while(max--) {
// accept 客户端连接
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
redisLog(REDIS_WARNING,
"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
// 为客户端创建客户端状态(redisClient)
acceptCommonHandler(cfd,0);
}
}
当新连接到来的时候,acceptCommonHandler处理新连接
static void acceptCommonHandler(int fd, int flags) {
// 为每个新连接创建客户端
redisClient *c;
if ((c = createClient(fd)) == NULL) {
redisLog(REDIS_WARNING,
"Error registering fd event for the new client: %s (fd=%d)",
strerror(errno),fd);
close(fd); /* May be already closed, just ignore errors */
return;
}
}
redisClient *createClient(int fd) {
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
...
}
}
可以看到,为新来的连接注册了readQueryFromClient,用来处理客户端发来的命令。
剩下来的就是解析协议,然后处理命令。然后得到命令的结果。根据对应的命令去回调不同的处理函数。
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
processInputBuffer(c);
}
实际上就是在命令表中的一张大表去查找命令。
明白了上面的流程处理,那么就很容易明白Redis的处理流程。那么Redis的网络编程部分就基本到此结束了。可以查看我的从零开始写一个Redis,谈到如何使用C++来写一个自己的Redis网络部分。