Bootstrap

从网络编程角度简析Redis源码流程

因为目前在学习网络编程,所以粗略看了一下Redis开源代码中关于这部分的代码,在此略做总结
参考资料:
对Redis源码做了中文备注的开源代码
Redis源码简要分析
Redis源代码分析之四:Unix底层网络通信——Anet
Redis运行流程源码解析


为了理解其大体流程图,需要了解一下Redis设计的事件驱动,之后再看一下初始化服务器中关于网络编程部分的内容。

事件

1.文件事件

对套接字操作的抽象

使用I/O多路复用程序同时监听多个套接字,(I/O多路复用程序:包装I/O多路复用函数库:select、epoll、evport、kqueue)
I/O多路复用程序将套接字放置一个队列中,当上一个socket产生的事件被处理完后,I/O多路复用程序才向文件时间分配器传下一个套接字
文件时间分配器根据socket产生的事件的类型,调用相应的处理器(实际上为函数),包括如下三种:

(1)连接应答处理器 ——acceptTcpHandler

当客户端用connect连接服务器套接字时,套接字产生AE_READABLE事件,该事件调用acceptTcpHandler函数,该函数主要工作如下:

cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
该函数大体如下:
    cfd = accept(s, sa, salen);      //s: fd
    inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
    *port = ntohs(s->sin_port);      //int *port = &cport
     acceptCommonHandler(cfd,0);    // 为客户端创建客户端状态(redisClient)

(2)命令请求处理器 ——readQueryFromClient

当connect-accept之后,服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联之后调用acceptCommonHandler(cfd, 0)
继续调用createClient(fd)
绑定读事件到事件loop(开始接收命令请求):
aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c)

接下来调用readQueryFromClient(有数据可读时的回调函数)

redisClient *c         //因I/O复用,需为每个客户端维持一个状态
int readlen= (unsigned)(c->bulklen+2)-sdslen(c->querybuf);
                        命令内容长度             查询缓冲区
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);   // 为查询缓存区分配空间
nread = read(fd, c->querybuf+qblen, readlen);    // 读入内容到查询缓存区(因为可
                                                 //能原来查询缓存区有数据)
                            qblen = sdslen(c->querybuf)
更新缓冲区(SDS)len、free大小,将末尾置'\0'
processInputBuffer(c);     // 从查询缓存重读取内容,创建参数,并执行命令

接下来通过:
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
        sendReplyToClient, c)
将执行命令的事件绑定到sendReplyToClient函数,即如下的命令回复处理器:

(3)命令回复处理器 ——sendReplyToClient

当服务器有命令回复需传给client时,server将client套接字的AE_WRITABLE事件和命令回复处理器关联,即sendReplyToClient,函数大体结构如下:

while(c->bufpos > 0 || listLength(c->reply))
    如下是c->bufpos > 0的情况执行的,listLength(c->reply)(回复链表)的略去先
    nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
    c->sentlen += nwritten;
    if (c->sentlen == c->bufpos) c->bufpos = 0;

    if (c->bufpos == 0 && listLength(c->reply) == 0)
        aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);  // 删除 write handler

对while(…)的解释:命令请求处理器(processInputBuffer)执行完后,先尝试将返回的内容添加到发送数据缓存区中(redisClient->buf)若该buf放不下这次要放的data / 已有data在排队(redisClient->reply链表不为空),则把data添加到发送链表的尾部。

server将命令回复保存到client的输出缓存区中,当client socket变为可读时,执行命令回复处理器,将输出缓存区的内容发给clilent

1.时间事件

正常模式下,Redis server只是用serverCron一个时间事件(周期性的),定期对自身的资源、状态进行检查和调整。

初始化服务器:

listenToPort( ): 打开TCP监听端口,等待客户端的命令请求。

读server.binaddr[j] : address we should bind to

getaddrinfo(bindaddr,_port,&hints,&servinfo)  //因规定了port,hint中ai_flags设                                 
                                         //为AI_PASSIVE,返回结果:用于监听绑定的
for (p = servinfo; p != NULL; p = p->ai_next) {
    s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)
                    AF_INET   SOCK_STREAM

    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))  //yes:1
    //设地址为可重用,为了在连接密集型的Redis程序中频繁打开/关闭socket时可重用本地地址、端口

    bind(s,b->ai_addr,b->ai_addrlen)
    listen(s, server.tcp_backlog)    //绑定并创建监听套接字

    flags = fcntl(fd, F_GETFL)
    fcntl(fd, F_SETFL, flags | O_NONBLOCK)    //将s置为非阻塞模式

之后是:
aeCreateFileEvent

aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL)
//为 TCP 连接关联连接应答(accept)处理器,用于接受并应答客户端的 connect() 调用

anetUnixServer( )
打开Unix本地端口,创建一本地连接用的服务器监听套接字

server.sofd = s = socket(AF_LOCAL, SOCK_STREAM, 0)
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))  //设地址可重用

struct sockaddr_un sa;  //进程间通信使用Unix(本地)套接字,而不用网络套接字
    {
        sun_family;     //AF_LOCAL / AF_UNIX
        sun_path;       //本地文件的路径
    }

bind(s, sa, sizeof(sa))
listen(s, server.tcp_backlog)

设server.sofd套接字为非阻塞模式
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL)
//文本地套接字关联应答处理器,从这里就回到了上一部分介绍的事件部分,串联到一起了。
;