因为目前在学习网络编程,所以粗略看了一下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)
//文本地套接字关联应答处理器,从这里就回到了上一部分介绍的事件部分,串联到一起了。