其实最开始想要看nginx的源代码的初衷就是为了搞懂它的nginx的事件驱动到底是怎么回事。。。到现在为止nginx代码的大体结构部分已经看的差不多了,对它的事件驱动也算是有了一个较为全面的了解,终于可以写这篇文章了。。。
首先用一张图来描述整个event的结构:
首先最高层的是epoll自己定义的事件结构:
- typedef union epoll_data {
- void *ptr; //指向相应的connection
- int fd;
- uint32_t u32;
- uint64_t u64;
- } epoll_data_t;
- struct epoll_event {
- uint32_t events;
- epoll_data_t data;
- };
- void *data; //指向发生当前事件的connection之类的数据
- unsigned accept:1; //用这个域来标记当前的事件是监听端口的accept事件
- unsigned active:1; //主要是用于表明是否实际将其加入了epoll
- unsigned ready:1; //判断当前事件是否已经发生了,例如当在epoll的wait返回后会将其置1
好了接下来来看如何将它们联系起来。。。。。首先来看如何将一个connection加入到epoll当中:
- //将某个连接加入到epoll当中
- static ngx_int_t
- ngx_epoll_add_connection(ngx_connection_t *c)
- {
- struct epoll_event ee;
- //这里表示当前连接关心的事件
- ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
- //这里event的data域的ptr指向的是当前的connection
- ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
- ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
- ”epoll add connection: fd:%d ev:%08XD”, c->fd, ee.events);
- //在epoll中直接将文件描述符以及关心的事件注册了就好了
- if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
- ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
- ”epoll_ctl(EPOLL_CTL_ADD, %d) failed”, c->fd);
- return NGX_ERROR;
- }
- c->read->active = 1;
- c->write->active = 1;
- return NGX_OK;
- }
接下来来看如何从epoll中获取事件,并将其与nginx的event相对应:
- //这里是定义的epoll模块具体处理事件的地方
- static ngx_int_t
- ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
- {
- int events;
- uint32_t revents;
- ngx_int_t instance, i;
- ngx_uint_t level;
- ngx_err_t err;
- ngx_event_t *rev, *wev, **queue;
- ngx_connection_t *c;
- /* NGX_TIMER_INFINITE == INFTIM */
- ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
- ”epoll timer: %M”, timer);
- //这里是epoll的wait,将得到的事件存到event_list里面,最大的事件量是nevents
- /*一开始就是等待事件,最长等待时间为timer;nginx为事件
- 专门用红黑树维护了一个计时器。后续对这个timer单独分析。
- */
- events = epoll_wait(ep, event_list, (int) nevents, timer); //这个超时事件是从红黑树里面获取的,当前最近的超时,这样可以保证epoll的wait能够在合适的时间内返回,保证定义的超时事件可以执行
- err = (events == -1) ? ngx_errno : 0;
- if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
- ngx_time_update();
- }
- if (err) {
- if (err == NGX_EINTR) {
- if (ngx_event_timer_alarm) {
- ngx_event_timer_alarm = 0;
- return NGX_OK;
- }
- level = NGX_LOG_INFO;
- } else {
- level = NGX_LOG_ALERT;
- }
- ngx_log_error(level, cycle->log, err, ”epoll_wait() failed”);
- return NGX_ERROR;
- }
- if (events == 0) {
- if (timer != NGX_TIMER_INFINITE) {
- return NGX_OK;
- }
- ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
- ”epoll_wait() returned no events without timeout”);
- return NGX_ERROR;
- }
- //这个锁貌似是线程的时候用吧。。没有搞懂
- ngx_mutex_lock(ngx_posted_events_mutex);
- //循环遍历所有产生的事件
- for (i = 0; i < events; i++) {
- c = event_list[i].data.ptr; //获取该事件实际对应的connection
- //instance 说白了就是个整形的变量
- instance = (uintptr_t) c & 1;
- c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
- rev = c->read;
- if (c->fd == -1 || rev->instance != instance) {
- /*
- * the stale event from a file descriptor
- * that was just closed in this iteration
- */
- ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
- ”epoll: stale event %p”, c);
- continue;
- }
- //获取发生的事件的类型
- revents = event_list[i].events;
- ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
- ”epoll: fd:%d ev:%04XD d:%p”,
- c->fd, revents, event_list[i].data.ptr);
- //如果发生了错误事件
- if (revents & (EPOLLERR|EPOLLHUP)) {
- ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
- ”epoll_wait() error on fd:%d ev:%04XD”,
- c->fd, revents);
- }
- #if 0
- if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
- ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
- ”strange epoll_wait() events fd:%d ev:%04XD”,
- c->fd, revents);
- }
- #endif
- if ((revents & (EPOLLERR|EPOLLHUP))
- && (revents & (EPOLLIN|EPOLLOUT)) == 0)
- {
- /*
- * if the error events were returned without EPOLLIN or EPOLLOUT,
- * then add these flags to handle the events at least in one
- * active handler
- */
- revents |= EPOLLIN|EPOLLOUT;
- }
- /*该事件是一个读事件,并该连接上注册的读事件是active的*/
- if ((revents & EPOLLIN) && rev->active) {
- if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
- rev->posted_ready = 1;
- } else {
- rev->ready = 1;
- }
- if (flags & NGX_POST_EVENTS) {
- //如果设置了NGX_POST_EVENTS,表示当前worker进程已经获取了锁,那么将获取的事件入队,因为可能是监听端口的accept事件,这里如果是监听端口的accept事件的话,那么该event的accept域会置为1 ,这个是在事件模块的worker进程初始化中会设置的
- //这里持有了锁就应该将产生的事件放入队列中,是为了能够在锁释放了以后再处理这些事件,这样可以让别的worker进程能够尽快的获取锁
- queue = (ngx_event_t **) (rev->accept ?
- &ngx_posted_accept_events : &ngx_posted_events);
- ngx_locked_post_event(rev, queue);
- } else {
- rev->handler(rev);
- }
- }
- wev = c->write;
- //如果是写事件,而且相应connection的写事件是激活的
- if ((revents & EPOLLOUT) && wev->active) {
- if (c->fd == -1 || wev->instance != instance) {
- /*
- * the stale event from a file descriptor
- * that was just closed in this iteration
- */
- ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
- ”epoll: stale event %p”, c);
- continue;
- }
- if (flags & NGX_POST_THREAD_EVENTS) {
- wev->posted_ready = 1;
- } else {
- wev->ready = 1;
- }
- if (flags & NGX_POST_EVENTS) {
- ngx_locked_post_event(wev, &ngx_posted_events);
- } else {
- wev->handler(wev);
- }
- }
- }
- ngx_mutex_unlock(ngx_posted_events_mutex);
- return NGX_OK;
- }
这样也就完成了整个事件的处理过程,也就是整个nginx的事件驱动的大体框架,。。。设计还是非常的巧妙的。。。另外就是还有一种定时事件类型,前面的一篇文章已经讲过了,Nginx自己采用红黑树来实现的。。。
好了,事件的大体模型也就写完了。。。也算是完成了当时的一个愿望吧。。。、