<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">想学习研究libevent怎么设计的,学习它的思想,学习它的设计,奈何自己实力不够啊,于是另辟奇径,从最早的版本开始,一个版本一个版本的学习,不信吃不透它。</span>
struct event { TAILQ_ENTRY (event) ev_read_next; TAILQ_ENTRY (event) ev_write_next; TAILQ_ENTRY (event) ev_timeout_next; TAILQ_ENTRY (event) ev_add_next; int ev_fd; short ev_events; struct timeval ev_timeout; void (*ev_callback)(int, short, void *arg); void *ev_arg; int ev_flags; };
以上为事件的结构体,libevent通过这个结构体管理事件
void event_init(void);
int event_dispatch(void); int timeout_next(struct timeval *); void timeout_process(void); void event_set(struct event *, int, short, void (*)(int, short, void *), void *); void event_add(struct event *, struct timeval *); void event_del(struct event *); int event_pending(struct event *, short, struct timeval *);
以上为libevent提供的接口,下面我们一个一个的详细分析他们
首先,事件初始化,初始化libevent
/*四个队列的头指针*/ TAILQ_HEAD (timeout_list, event) timequeue; TAILQ_HEAD (event_wlist, event) writequeue; TAILQ_HEAD (event_rlist, event) readqueue; TAILQ_HEAD (event_ilist, event) addqueue;
接着是libevent的核心循环函数
/* 事件处理的主循环:int event_dispatch(void) 功能描述:事件处理的主循环,循环监听,调用事件的回调函数处理 函数实现:1.开始循环之前,先调用函数event_recalc()分配合适的fd_set 2.进入处理循环 (1).遍历读事件队列和写事件队列,将描述符添加到fd_set中 (2).调用timeout_next(),得到阻塞等待时间 (3).调用select监听 (4).遍历读写队列,看看有那个事件被触发,调用对应的回调函数,并将其从队列中移出 如果没有被触发,它们将仍被保留在队列中,这个时候我们从它们中再次找到最大文件描述符 并且,在处理期间,要将inloop标记置位,防止在读写队列期间,再将新的事件移入队列 (5).处理完之后,遍历add队列,将add队列中的事件,对应投入读或者写队列中 (6).根据新的最大描述符,调用event_recalc()重新分配fd_set空间 (7).调用timeout_process()处理 */ int event_dispatch(void) { struct timeval tv; struct event *ev, *old; int res, maxfd; /* Calculate the initial events that we are waiting for */ //1.开始循环之前,重新计算分配描述符集合大小 if (events_recalc(0) == -1) return (-1); while (1) { //将读写事件集全部清0,类似于FD_ZERO memset(event_readset, 0, event_fdsz); memset(event_writeset, 0, event_fdsz); //遍历写事件集合队列 TAILQ_FOREACH(ev, &writequeue, ev_write_next) //将写事件集合中的描述符,添加道select监听的写数据集 FD_SET(ev->ev_fd, event_writeset); //遍历读事件集合队列 TAILQ_FOREACH(ev, &readqueue, ev_read_next) //遍历读事件集合队列,将事件描述符添加到select监听 FD_SET(ev->ev_fd, event_readset); //时间设置 timeout_next(&tv); if ((res = select(event_fds + 1, event_readset, event_writeset, NULL, &tv)) == -1) { if (errno != EINTR) { log_error("select"); return (-1); } continue; } LOG_DBG((LOG_MISC, 80, __FUNCTION__": select reports %d", res)); maxfd = 0; event_inloop = 1; for (ev = TAILQ_FIRST(&readqueue); ev;) { //从读事件队头拿出读事件 old = TAILQ_NEXT(ev, ev_read_next); if (FD_ISSET(ev->ev_fd, event_readset)) { //看看这个事件是否就绪 event_del(ev); //从事件队列中删除事件 (*ev->ev_callback)(ev->ev_fd, EV_READ, ev->ev_arg); //调用事件注册的处理函数 } else if (ev->ev_fd > maxfd) //否则找最大描述符 maxfd = ev->ev_fd; ev = old; } for (ev = TAILQ_FIRST(&writequeue); ev;) { old = TAILQ_NEXT(ev, ev_read_next); if (FD_ISSET(ev->ev_fd, event_writeset)) { event_del(ev); (*ev->ev_callback)(ev->ev_fd, EV_WRITE, ev->ev_arg); } else if (ev->ev_fd > maxfd) maxfd = ev->ev_fd; ev = old; } event_inloop = 0; for (ev = TAILQ_FIRST(&addqueue); ev; ev = TAILQ_FIRST(&addqueue)) { TAILQ_REMOVE(&addqueue, ev, ev_add_next); ev->ev_flags &= ~EVLIST_ADD; event_add_post(ev); if (ev->ev_fd > maxfd) maxfd = ev->ev_fd; } if (events_recalc(maxfd) == -1) return (-1); timeout_process(); } return (0); }
在这个主循环函数中,它调用了
events_recalc()这个函数,那么这个函数是干嘛的呢?
<pre name="code" class="cpp">/* 分配事件集fd_set大小的函数:int event_recalc(int max) 功能描述:根据,最大事件描述符,分配对应大小的事件集,在unix中,fd_set是通过对应位表示事件描述符的,所以,事件集的大小要足够大 参数为最大描述符,根据所传入的参数,分配最大fd_set空间 如果,传入的参数为0,函数会自己遍历读写队列,找到最大描述符,分配fd_set空间 函数实现:1.将传入的参数,赋值给存储最大描述符的全局变量event_fds 2.判断最大描述符是否为0,是就遍历读写队列,找到队列中最大的描述符 3.计算,表示最大描述符,所需要的fd_set字节数 4.全局变量event_fdsz存储当前fd_set的字节大小,将最新计算出的fd_set大小和event_fdsz进行比较 如果,最新计算出的所需大小大于当前fd_set的大小,就重新分配读写集合的空间 更新全局变量event_fds,event_fdsz,event_readset,event_writeset的值 */ int events_recalc(int max) { //读写,描述符·集合 fd_set *readset, *writeset; //描述事件的结构体 struct event *ev; int fdsz; //最大文件描述符在描述符集合中 event_fds = max; //如果最大传入描述符为0 if (!event_fds) { //在写队列中遍历找最大描述符 TAILQ_FOREACH(ev, &writequeue, ev_write_next) if (ev->ev_fd > event_fds) event_fds = ev->ev_fd; //再去遍历读队列 TAILQ_FOREACH(ev, &readqueue, ev_read_next) if (ev->ev_fd > event_fds) event_fds = ev->ev_fd; //最后event_fds中是最大描述符 } //得到fd_set占字节数 fdsz = howmany(event_fds + 1, NFDBITS) * sizeof(fd_mask); if (fdsz > event_fdsz) { if ((readset = realloc(event_readset, fdsz)) == NULL) { log_error("malloc"); return (-1); } if ((writeset = realloc(event_writeset, fdsz)) == NULL) { log_error("malloc"); free(readset); return (-1); } memset(readset + event_fdsz, 0, fdsz - event_fdsz); memset(writeset + event_fdsz, 0, fdsz - event_fdsz); event_readset = readset; event_writeset = writeset; event_fdsz = fdsz; } return (0); }
/*
初始化事件:void event_set(struct event *ev, int fd, short events,void (*callback)(int, short, void *), void *arg)
功能描述:设置所传入的事件结构体的属性,完成对事件的初始化
函数实现:1.设置事件的回调函数
2.设置事件的参数ev_arg,它是回调函数的第三个参数
3.设置事件的参数ev_fd,它是回调函数的第一个参数
4.设置事件的参数ev_events,它是回调的第二个参数,也代表事件的类型(读、写、超时)
*/
void
event_set(struct event *ev, int fd, short events,
void (*callback)(int, short, void *), void *arg)
{
ev->ev_callback = callback;
ev->ev_arg = arg;
ev->ev_fd = fd;
ev->ev_events = events;
ev->ev_flags = EVLIST_INIT;
}
/* <span style="white-space:pre"> </span>将事件添加到对应的读或者写队列:void event_add(struct event*ev,struct timeval* tv) <span style="white-space:pre"> </span>功能描述:将事件添加到对应的读写队列中 <span style="white-space:pre"> </span>函数实现:1.如果阻塞等待时间不为空 <span style="white-space:pre"> </span>(1).得到当前时间 <span style="white-space:pre"> </span>(2).将当前时间与阻塞时间相加得到超时时间 <span style="white-space:pre"> </span>(3).判断事件是否在超时队列中 <span style="white-space:pre"> </span>如果在超时队列中,将事件从队列中移出 <span style="white-space:pre"> </span>(4).在超时队列中寻找合适的位置(超时队列,将超时时间从小到大排列) <span style="white-space:pre"> </span>如果找到合适的位置,将其插入到该位置 <span style="white-space:pre"> </span>如果没有找到,将其从队尾插入 <span style="white-space:pre"> </span>(5).将事件在超时队列的标记置位 <span style="white-space:pre"> </span> 2.如果事件正在处理中 <span style="white-space:pre"> </span>(1).通过检测标记位判断当前被插入的事件是否之前就已经在add队列中 <span style="white-space:pre"> </span>如果已经在队列中,直接返回 <span style="white-space:pre"> </span>如果没有,将其加入add队列中,并将其标记置位 <span style="white-space:pre"> </span> 3.否则 <span style="white-space:pre"> </span>(1).调用event_add_post()将其加入到对应的读写队列中 */ void event_add(struct event *ev, struct timeval *tv) { <span style="white-space:pre"> </span>LOG_DBG((LOG_MISC, 55, <span style="white-space:pre"> </span> "event_add: event: %p, %s%s%scall %p", <span style="white-space:pre"> </span> ev, <span style="white-space:pre"> </span> ev->ev_events & EV_READ ? "EV_READ " : " ", <span style="white-space:pre"> </span> ev->ev_events & EV_WRITE ? "EV_WRITE " : " ", <span style="white-space:pre"> </span> tv ? "EV_TIMEOUT " : " ", <span style="white-space:pre"> </span> ev->ev_callback)); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//如果等待时间不为空 <span style="white-space:pre"> </span>if (tv != NULL) { <span style="white-space:pre"> </span>struct timeval now; <span style="white-space:pre"> </span>struct event *tmp; <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//1.得到当前时间 <span style="white-space:pre"> </span>gettimeofday(&now, NULL); <span style="white-space:pre"> </span>//2.将当前时间与阻塞等待时间相加,得到超时时间 <span style="white-space:pre"> </span>timeradd(&now, tv, &ev->ev_timeout); <span style="white-space:pre"> </span>LOG_DBG((LOG_MISC, 55, <span style="white-space:pre"> </span> "event_add: timeout in %d seconds, call %p", <span style="white-space:pre"> </span> tv->tv_sec, ev->ev_callback)); <span style="white-space:pre"> </span>//如果有超时队列标记(即之前在队列中存在) <span style="white-space:pre"> </span>if (ev->ev_flags & EVLIST_TIMEOUT) <span style="white-space:pre"> </span>//将事件从时间队列移出 <span style="white-space:pre"> </span>TAILQ_REMOVE(&timequeue, ev, ev_timeout_next); <span style="white-space:pre"> </span>/* Insert in right temporal order */ <span style="white-space:pre"> </span>//再次将时间事件找到正确的队列位置插入 <span style="white-space:pre"> </span>for (tmp = TAILQ_FIRST(&timequeue); tmp; <span style="white-space:pre"> </span> tmp = TAILQ_NEXT(tmp, ev_timeout_next)) { <span style="white-space:pre"> </span> if (timercmp(&ev->ev_timeout, &tmp->ev_timeout, <=)) <span style="white-space:pre"> </span> break; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (tmp) <span style="white-space:pre"> </span>TAILQ_INSERT_BEFORE(tmp, ev, ev_timeout_next); <span style="white-space:pre"> </span>else <span style="white-space:pre"> </span>TAILQ_INSERT_TAIL(&timequeue, ev, ev_timeout_next); <span style="white-space:pre"> </span>//再将时间队列标记置位 <span style="white-space:pre"> </span>ev->ev_flags |= EVLIST_TIMEOUT; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//如果事件正在循环中,判断一下被插入的事件是否是从添加等待队列中取出来的,如果是,直接返回,不是,将它添加到等待队列返回 <span style="white-space:pre"> </span>if (event_inloop) { <span style="white-space:pre"> </span>/* We are in the event loop right now, we have to <span style="white-space:pre"> </span> * postpone the change until later. <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>if (ev->ev_flags & EVLIST_ADD) <span style="white-space:pre"> </span>return; <span style="white-space:pre"> </span>TAILQ_INSERT_TAIL(&addqueue, ev, ev_add_next); <span style="white-space:pre"> </span>ev->ev_flags |= EVLIST_ADD; <span style="white-space:pre"> </span>} else <span style="white-space:pre"> </span>event_add_post(ev); }
/* <span style="white-space:pre"> </span>将事件添加到对应的读或者写队列 <span style="white-space:pre"> </span>功能描述:通过判断事件的ev_events标记将其放入对应的队列 <span style="white-space:pre"> </span>函数实现:1.判断事件是读事件,并且事件没有在read队列中,将其添加到读队列,读队列标记置位 <span style="white-space:pre"> </span> 2.判断事件是写事件,并且事件没有在write队列中,将其添加到写队列,写队列标记置位 */ void event_add_post(struct event *ev) { <span style="white-space:pre"> </span>//如果,事件是读事件,并且没有在读队列中,就添加到读队列 <span style="white-space:pre"> </span>if ((ev->ev_events & EV_READ) && !(ev->ev_flags & EVLIST_READ)) { <span style="white-space:pre"> </span>TAILQ_INSERT_TAIL(&readqueue, ev, ev_read_next); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>ev->ev_flags |= EVLIST_READ; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>//如果,事件为写事件,并且没有在写队列,就添加到写队列 <span style="white-space:pre"> </span>if ((ev->ev_events & EV_WRITE) && !(ev->ev_flags & EVLIST_WRITE)) { <span style="white-space:pre"> </span>TAILQ_INSERT_TAIL(&writequeue, ev, ev_write_next); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>ev->ev_flags |= EVLIST_WRITE; <span style="white-space:pre"> </span>} } /* <span style="white-space:pre"> </span>删除事件 <span style="white-space:pre"> </span>功能描述:把事件从所在的队列中清除 <span style="white-space:pre"> </span>函数实现:1.判断事件在读或者写或者添加或者超时队列中,将其从对应队列中删除,将其flag位置0 */ void event_del(struct event *ev) { <span style="white-space:pre"> </span>LOG_DBG((LOG_MISC, 80, "event_del: %p, callback %p", <span style="white-space:pre"> </span> ev, ev->ev_callback)); <span style="white-space:pre"> </span>if (ev->ev_flags & EVLIST_ADD) { <span style="white-space:pre"> </span>TAILQ_REMOVE(&addqueue, ev, ev_add_next); <span style="white-space:pre"> </span>ev->ev_flags &= ~EVLIST_ADD; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (ev->ev_flags & EVLIST_TIMEOUT) { <span style="white-space:pre"> </span>TAILQ_REMOVE(&timequeue, ev, ev_timeout_next); <span style="white-space:pre"> </span>ev->ev_flags &= ~EVLIST_TIMEOUT; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (ev->ev_flags & EVLIST_READ) { <span style="white-space:pre"> </span>TAILQ_REMOVE(&readqueue, ev, ev_read_next); <span style="white-space:pre"> </span>ev->ev_flags &= ~EVLIST_READ; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (ev->ev_flags & EVLIST_WRITE) { <span style="white-space:pre"> </span>TAILQ_REMOVE(&writequeue, ev, ev_write_next); <span style="white-space:pre"> </span>ev->ev_flags &= ~EVLIST_WRITE; <span style="white-space:pre"> </span>} } /* <span style="white-space:pre"> </span>获得新的阻塞时间 <span style="white-space:pre"> </span>功能描述:获得新的阻塞时间 <span style="white-space:pre"> </span>函数实现:1.如果超时队列没有元素,返回默认超时时间 <span style="white-space:pre"> </span> 2.如果有事件 <span style="white-space:pre"> </span>(1).得到当前时间 <span style="white-space:pre"> </span>(2).和队头事件的超时时间比较,如果已经超时,说明出现问题,直接清空超时时间,不阻塞 <span style="white-space:pre"> </span>(3).没超时,就将剩余的阻塞时间计算出来,作为新的阻塞时间 */ int timeout_next(struct timeval *tv) { <span style="white-space:pre"> </span>//当前时间 <span style="white-space:pre"> </span>struct timeval now; <span style="white-space:pre"> </span>//指向事件的指针 <span style="white-space:pre"> </span>struct event *ev; <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//定时器队头事件指针是否为空 <span style="white-space:pre"> </span>if ((ev = TAILQ_FIRST(&timequeue)) == NULL) { <span style="white-space:pre"> </span>//清空时间 <span style="white-space:pre"> </span>timerclear(tv); <span style="white-space:pre"> </span>//定时默认5,返回 <span style="white-space:pre"> </span>tv->tv_sec = TIMEOUT_DEFAULT; <span style="white-space:pre"> </span>return (0); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span> <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//得到当前时间 <span style="white-space:pre"> </span>if (gettimeofday(&now, NULL) == -1) <span style="white-space:pre"> </span>return (-1); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//和定时器队头事件定义的超时时间比较 <span style="white-space:pre"> </span>if (timercmp(&ev->ev_timeout, &now, <=)) { <span style="white-space:pre"> </span>//不超时,清空结构 <span style="white-space:pre"> </span>timerclear(tv); <span style="white-space:pre"> </span>return (0); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>//定时器事件超时出的时间,更新到tv中 <span style="white-space:pre"> </span>timersub(&ev->ev_timeout, &now, tv); <span style="white-space:pre"> </span>LOG_DBG((LOG_MISC, 60, "timeout_next: in %d seconds", tv->tv_sec)); <span style="white-space:pre"> </span>return (0); } /* <span style="white-space:pre"> </span>超时后的处理 <span style="white-space:pre"> </span>功能描述:将超时队列中所有超时的事件从队列中移出,并且执行其对应的回调 <span style="white-space:pre"> </span>函数实现:1.获得当前时间 <span style="white-space:pre"> </span> 2.只要超时队列不为空,进入到队列中 <span style="white-space:pre"> </span>(1).从队头获得的超时时间比当前时间大,说明整个队列中都没有超时,跳出循环 <span style="white-space:pre"> </span>(2).否则,将队头从队列中移出,将队头事件的超时队列标记位置0,调用对应的超时回调处理,继续循环 */ void timeout_process(void) { <span style="white-space:pre"> </span>struct timeval now; <span style="white-space:pre"> </span>struct event *ev; <span style="white-space:pre"> </span>gettimeofday(&now, NULL); <span style="white-space:pre"> </span>while ((ev = TAILQ_FIRST(&timequeue)) != NULL) { <span style="white-space:pre"> </span>if (timercmp(&ev->ev_timeout, &now, >)) <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>TAILQ_REMOVE(&timequeue, ev, ev_timeout_next); <span style="white-space:pre"> </span>ev->ev_flags &= ~EVLIST_TIMEOUT; <span style="white-space:pre"> </span>LOG_DBG((LOG_MISC, 60, "timeout_process: call %p", <span style="white-space:pre"> </span> ev->ev_callback)); <span style="white-space:pre"> </span>(*ev->ev_callback)(ev->ev_fd, EV_TIMEOUT, ev->ev_arg); <span style="white-space:pre"> </span>} }
时间: 2024-10-14 15:47:56