nginx事件驱动总结

其实最开始想要看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;
};
用ptr指针来指向所属的connection,解析来就是nginx的connection结构了,其有两个先关的定义如下:
    ngx_event_t        *read;  //读事件
    ngx_event_t        *write;  //写事件
一看就能明白了吧,read表示可以读取的事件,write表示可以写的事件,接下来就是来看nginx的事件的定义了:
    void            *data;    //指向发生当前事件的connection之类的数据
    unsigned         accept:1;   //用这个域来标记当前的事件是监听端口的accept事件
    unsigned         active:1;     //主要是用于表明是否实际将其加入了epoll
    unsigned         ready:1;     //判断当前事件是否已经发生了,例如当在epoll的wait返回后会将其置1
   ngx_rbtree_node_t   timer;  //对应的红黑树的节点
ngx_event_handler_pt  handler;   //事件的处理函数
其用data域指向所属的connection,用accept来区分是否是一个accept事件。。。,当为定时事件时timer域有用,它是一个红黑树的节点结构,handler是该事件的处理函数。。。

好了接下来来看如何将它们联系起来。。。。。首先来看如何将一个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类型的事件,然后将其data的ptr域指向当前的connection,接着再调用epoll的epoll_ctl函数将其加入到epoll当中也就完成了connection的加入。。。。


接下来来看如何从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;
}
上述代码还是非常简单的,说白了就是调用epoll的wait函数,获取epoll的event,接着根据其的data的ptr域找到所属的connection,然后根据epoll的事件类型来对应connection中的read与write事件,并将其加入到事件队列中,待会再调用其的处理函数来处理。。。

这样也就完成了整个事件的处理过程,也就是整个nginx的事件驱动的大体框架,。。。设计还是非常的巧妙的。。。另外就是还有一种定时事件类型,前面的一篇文章已经讲过了,nginx自己采用红黑树来实现的。。。

好了,事件的大体模型也就写完了。。。也算是完成了当时的一个愿望吧。。。、


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值