一、aeCreateEventLoop & aeCreateFileEvent

上一篇文章中,我们已经将服务器启动,只是其中有些细节我们跳过了,比如aeCreateEventLoop函数到底做了什么? 接下来我们要分析ae.c文件,它是整个Redis网络事件框架,其中定义了各个管理事件的函数,比如aeCreateFileEventaeDeleteFileEvent分别是注册新的事件和删除事件。

其实aeCreateEventLoop的作用主要是给server->loop申请空间。

//ae.c

aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop; if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) { //给eventLoop申请空间
goto err;
} eventLoop->events = zmalloc(sizeof(aeFileEvent) * setsize); //给events链表申请空间
eventLoop->fired = zmalloc(sizeof(aeFiredEvent) * setsize); //给fired链表申请空间
if (eventLoop->events == NULL || eventLoop->fired == NULL) {
goto err;
} eventLoop->setsize = setsize; //设置大小
eventLoop->lastTime = time(NULL); //设置lastTime=now
eventLoop->timeEventHead = NULL; //定时事件链表置空
eventLoop->timeEventNextId = 0; //定时事件的id为0
eventLoop->stop = 0; //stop为0
eventLoop->maxfd = -1; //最大文件描述符为0
eventLoop->beforesleep = NULL; //beforesleep设置为NULL if (aeApiCreate(eventLoop) == -1) { //给EPOLL申请空间
goto err;
} /* Events with mask == AE_NONE are not set. So let's initialize the vector with it. */
for (int i = 0; i < setsize; i++) {
eventLoop->events[i].mask = AE_NONE; //将每一个fd的事件初始化为0
} return eventLoop; err:
if (eventLoop) {
zfree(eventLoop->events);
zfree(eventLoop->fired);
zfree(eventLoop);
} return NULL;
}

至此申请空间完成,我们整个EventLoop结构如下图所示:

我们完成了所有空间的申请和初始化工作:

loop->events : 是一个aeFileEvent 数组,大小为 setsize 。
loop->fired : 是一个aeFiredEvent 数组,大小也为 setsize 。
loop->timeEventHead :目前为NULL
loop->apidata:指向aeApiState,包含epfd和epoll_event数组。

接着我们调用anetTcpServer返回了listen_fdanetTcpServer我们在anet.c分析的时候再说,接下来重点是我们调用aeCreateFileEventlisten_fd注册到epfd上的过程。

//ae.c

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData) {
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
aeFileEvent *fe = &eventLoop->events[fd]; //利用fe指向eventLoop->events[listen_fd] if (aeApiAddEvent(eventLoop, fd, mask) == -1) { //本质是调用epoll_ctl(epfd,EPOLL_CTL_ADD,fd,...);
return AE_ERR;
} fe->mask |= mask; //如果fe->mask之前不是空,现在就相当于同时监控两个事件
if (mask & AE_READABLE) {
fe->rfileProc = proc; //说明proc是读操作的处理函数
} if (mask & AE_WRITABLE) {
fe->wfileProc = proc; //说明proc是写操作的处理函数
} fe->clientData = clientData; //让它们指向同一个client或者server实例
if (fd > eventLoop->maxfd) {
eventLoop->maxfd = fd; //如果新的fd大于maxfd,则更新maxfd
} return AE_OK;
}

此时我们的整个EventLoop变成了下面这个样子:

可以看到:

1 : loop->events[4].mask  = 1 , 表示读,rfileProc 为 acceptTcpHandler。 因为它是listen_fd,负责接受连接。为什么是 4 呢?因为 3 已经作为 epfd 的文件描述符了。

2 : 我们将 fd = 4 & EPOLLIN事件注册给了epfd。

现在就等着来新的连接了,因为这样的话一旦检测到listen_fd上有数据可读,那就会调用acceptTcpHandler接受连接,这也是回掉机制的一种体现:我们现在已经给listen_fd注册了相应的回掉函数了,等着事件发生,然后去调用注册好的函数。我们继续往下走继续看这个过程:

二、aeProcessEvents & acceptTcpHandler

继续向下会进入aeMain,之后一直轮询调用aeProcessEvents,接下来我们分析下aeProcessEvents到底是怎么处理各类事件的:

//ae.c

int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
int processed = 0, numevents; /* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) {
//如果flags什么事件都没有监听,return 0
return 0;
} /* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
/* 注意,我们即使没有文件事件,但是仍然想调用select/epoll,让其阻塞直到我们想处理的
* 定时事件发生为止*/ if (eventLoop->maxfd != -1 || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
//如果有定时事件处理
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp; if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) {
// for 循环查找到最近需要发生的定时事件
shortest = aeSearchNearestTimer(eventLoop);
} if (shortest) {
long now_sec, now_ms;
aeGetTime(&now_sec, &now_ms);
tvp = &tv; /* How many milliseconds we need to wait for the next time event to fire? */
/* 计算我们需要等待的ms数,直到最近的定时事件发生*/
long long ms = (shortest->when_sec - now_sec) * 1000 + shortest->when_ms - now_ms; if (ms > 0) {
//如果定时事件没有过期,计算出需要等待的时间,作为epoll_wait的第四个参数
tvp->tv_sec = ms / 1000;
tvp->tv_usec = (ms % 1000) * 1000;
} else {
//否则置为0,epoll_wait就不会阻塞
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout to zero */
/*如果没有找到定时事件 */
if (flags & AE_DONT_WAIT) { //设置了AE_DONT_WAIT操作,就不等
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else { //否则就阻塞等待直到事件发生
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
} numevents = aeApiPoll(eventLoop, tvp); //调用epoll_wait函数,返回需要处理的事件列表 for (int i = 0; i < numevents; i++) { //遍历依次处理loop->fired
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[i].fd];
int mask = eventLoop->fired[i].mask;
int fd = eventLoop->fired[i].fd; int rfired = 0;
/* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn't
* processed, so we check if the event is still valid. */
if (fe->mask & mask & AE_READABLE) {
rfired = 1; //确保读或者写只执行一个
fe->rfileProc(eventLoop, fd, fe->clientData, mask); //执行读处理
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop, fd, fe->clientData, mask);
}
}
processed++;
}
}
/* Check time events */
/* 处理所有的时间事件 */
if (flags & AE_TIME_EVENTS) {
processed += processTimeEvents(eventLoop);
} return processed; /* return the number of processed file/time events */
}

假设 listen_fd此时发生了事件,那一定是有新的连接过来,fe->rfileProc(eventLoop, fd, fe->clientData, mask) 就会使用 acceptTcpHandler接受连接:

static void acceptTcpHandler(aeEventLoop *loop, int fd, void *data, int mask)
{
char cip[64];
int cport; server_t *server = (server_t *)data; int cfd = anetTcpAccept(NULL, fd, cip, sizeof(cip), &cport); //调用accept接受连接
if (cfd != -1) {
printf("accepted ip %s:%d\n", cip, cport);
anetNonBlock(NULL, cfd); //设置socket非阻塞
anetEnableTcpNoDelay(NULL, cfd); //开启TcpNoDelay选项
client_t *client = alloc_client(); //申请一个新的客户端
if (!client) {
printf("alloc client error...close socket\n");
close(fd);
return;
} client->loop = loop;
client->fd = cfd; if (aeCreateFileEvent(loop, cfd, AE_READABLE, readEventHandler, client) == AE_ERR) {
//继续调用aeCreateFileEvent给新连接的fd注册可读事件,并且注册读函数readEventHandler
if (errno == ERANGE) {
// or use aeResizeSetSize(server->loop, cfd) modify this limit
printf("so many client, close new.");
} else {
printf("create socket readable event error, close it.");
}
free_client(client);
}
}
}

处理到这里,算是接受了一个连接,至于以后的读写操作,等到下次再分析~

Redis网络库源码分析(3)之ae.c的更多相关文章

  1. Redis网络库源码分析(1)之介绍篇

    一.前言 Redis网络库是一个单线程EPOLL模型的网络库,和Memcached使用的libevent相比,它没有那么庞大,代码一共2000多行,因此比较容易分析.其实网上已经有非常多有关这个网络库 ...

  2. Redis网络库源码分析(2)之启动服务器

    一.从main开始 main函数定义在server.c中,它的内容如下: //server.c int main() { signal(SIGPIPE, SIG_IGN); //忽略SIGPIPE信号 ...

  3. 第08课:【实战】Redis网络通信模块源码分析(1)

    我们这里先研究redis-server端的网络通信模块.除去Redis本身的业务功能以外,Redis的网络通信模块实现思路和细节非常有代表性.由于网络通信模块的设计也是Linux C++后台开发一个很 ...

  4. 第10课:[实战] Redis 网络通信模块源码分析(3)

    redis-server 接收到客户端的第一条命令 redis-cli 给 redis-server 发送的第一条数据是 *1\r\n\$7\r\nCOMMAND\r\n .我们来看下对于这条数据如何 ...

  5. Redis事件库源码分析

    由于老大在新项目中使用redis的事件库代替了libevent,我也趁着机会读了一遍redis的事件库代码,第一次读到“优美,让人愉快”的代码,加之用xmind制作的类图非常帅,所以留文纪念. Red ...

  6. 第09课:【实战】Redis网络通信模块源码分析(2)

    侦听 fd 与客户端 fd 是如何挂载到 EPFD 上去的 同样的方式,要把一个 fd 挂载到 EPFD 上去,需要调用系统 API epoll_ctl ,搜索一下这个函数名.在文件 ae_epoll ...

  7. # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#

    Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析 Volley源码一共40多个类和接口.除去一些工具类的实现,核心代码只有20多个类.所以相对来说分析起来没有那么吃力.但是要想分析透 ...

  8. Redis网络模型的源码分析

    Redis的网络模型是基于I/O多路复用程序来实现的.源码中包含四种多路复用函数库epoll.select.evport.kqueue.在程序编译时会根据系统自动选择这四种库其中之一.下面以epoll ...

  9. Android网络框架源码分析一---Volley

    转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...

随机推荐

  1. navicat导出DDL语句

    工作中有的时候需要将某个库中的表.视图.函数.存储过程等创建语句导出,又不需要表中的数据. 方法一:需要拷贝的创建语句条数不多,可以选择直接拷贝DDL语句 方法二:使用Navicat的备份功能

  2. deepin(debian)中双网卡上内外网的设置方法(通过NetworkManager运行脚本)

    国产良心操作系统deepin,界面好看,反应速度快,开箱即用,深度商店里有非常多好用的linux.windows软件,其windows软件通过crossover进行运行,还可以运行一些安卓的apk程序 ...

  3. CF618F-Double Knapsack【结论】

    正题 题目链接:https://www.luogu.com.cn/problem/CF618F 题目大意 给出大小为\(n\),值域为\([1,n]\)的两个可重集合\(A,B\) 需要你对它们各求出 ...

  4. P4494-[HAOI2018]反色游戏【圆方树】

    正题 题目链接:https://www.luogu.com.cn/problem/P4494 题目大意 给出\(n\)个点\(m\)条边的一张无向图,节点有\(0/1\),每条边可以选择是否取反两边的 ...

  5. [洛谷日报#204] StackEdit——Markdown 编辑器的功能介绍

    本文同时发表于洛谷日报,您也可以通过洛谷博客进行查看. 1.介绍与开始使用 1.1 这是什么? StackEdit是基于PageDown.Stack Overflow和其他堆栈交换站点使用的Markd ...

  6. 初探计算机网络之HTTPS请求

    ​ HTTPS自诞生以来,我们总是对它充满着很多的疑问,HTTPS到底是啥?HTTPS多出来的S指的是什么?HTTPS安全可靠吗?访问一个HTTPS的网站的流程等等,带着这些疑问,我们一起来揭开HTT ...

  7. 微信小程序 开发 “婚礼邀请函”

    成品展示: 5个页面 我们来讲解哈(上面地图位置随便定的点) 1.首页开发 一开始进来显示首页  然后默认开始播放背景音乐,这个背景音乐点击右上角图标可以暂停(有动画),然后点击新郎和新娘文字可以调到 ...

  8. Lynis 漏洞扫描工具部署及效果展示

    Lynis 漏洞扫描工具部署及效果展示 介绍 Lynis是一个安全审计工具,它可以在Linux,macOS和其他基于Unix的系统上运行.Lynis的主要重点是执行系统的运行状况检查,它还有助于检测漏 ...

  9. 10-1 Python 学习笔记

    1. 项目 在文本编辑器中新建一个文件,写几句话来总结一下你至此学到的 Python 知识,其中每一行都以"In Python you can"打头. 将这个文件命名为learni ...

  10. 项目实战:Qt文件改名工具 v1.2.0(支持递归检索,搜索:模糊匹配,前缀匹配,后缀匹配;重命名:模糊替换,前缀追加,后缀追加)

    需求   在整理文件和一些其他头文件的时候,需要对其名称进行整理和修改,此工具很早就应该写了,创业后,非常忙,今天抽空写了一个顺便提供给学习.   工具和源码下载地址   本篇文章的应用包和源码包可在 ...