Nginx事件驱动总结

其实最开始想要看nginx的源代码的初衷就是为了搞懂它的nginx的事件驱动到底是怎么回事。。。到现在为止nginx代码的大体结构部分已经看的差不多了,对它的事件驱动也算是有了一个较为全面的了解,终于可以写这篇文章了。。。

首先用一张图来描述整个event的结构:

首先最高层的是epoll自己定义的事件结构:

  1. typedef union epoll_data {  
  2.     void         *ptr;   //指向相应的connection  
  3.     int           fd;  
  4.     uint32_t      u32;  
  5.     uint64_t      u64;  
  6. } epoll_data_t;  
  7.   
  8. struct epoll_event {  
  9.     uint32_t      events;  
  10.     epoll_data_t  data;  
  11. };  
用ptr指针来指向所属的connection,解析来就是nginx的connection结构了,其有两个先关的定义如下:
  1. ngx_event_t        *read;  //读事件  
  2. ngx_event_t        *write;  //写事件  
一看就能明白了吧,read表示可以读取的事件,write表示可以写的事件,接下来就是来看nginx的事件的定义了:
  1. void            *data;    //指向发生当前事件的connection之类的数据  
  2. unsigned         accept:1;   //用这个域来标记当前的事件是监听端口的accept事件  
  3. unsigned         active:1;     //主要是用于表明是否实际将其加入了epoll  
  4. unsigned         ready:1;     //判断当前事件是否已经发生了,例如当在epoll的wait返回后会将其置1  
  1.    ngx_rbtree_node_t   timer;  //对应的红黑树的节点  
  1. ngx_event_handler_pt  handler;   //事件的处理函数  
其用data域指向所属的connection,用accept来区分是否是一个accept事件。。。,当为定时事件时timer域有用,它是一个红黑树的节点结构,handler是该事件的处理函数。。。

好了接下来来看如何将它们联系起来。。。。。首先来看如何将一个connection加入到epoll当中:

  1. //将某个连接加入到epoll当中  
  2. static ngx_int_t  
  3. ngx_epoll_add_connection(ngx_connection_t *c)  
  4. {  
  5.     struct epoll_event  ee;  
  6.   
  7. //这里表示当前连接关心的事件  
  8.     ee.events = EPOLLIN|EPOLLOUT|EPOLLET;  
  9. //这里event的data域的ptr指向的是当前的connection  
  10.     ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);  
  11.   
  12.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,  
  13.                    "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);  
  14. //在epoll中直接将文件描述符以及关心的事件注册了就好了  
  15.     if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {  
  16.         ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,  
  17.                       "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);  
  18.         return NGX_ERROR;  
  19.     }  
  20.   
  21.     c->read->active = 1;  
  22.     c->write->active = 1;  
  23.   
  24.     return NGX_OK;  
  25. }  
函数还是比较简单的,首先是声明了一个epoll类型的事件,然后将其data的ptr域指向当前的connection,接着再调用epoll的epoll_ctl函数将其加入到epoll当中也就完成了connection的加入。。。。


接下来来看如何从epoll中获取事件,并将其与nginx的event相对应:

  1. //这里是定义的epoll模块具体处理事件的地方  
  2. static ngx_int_t  
  3. ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)  
  4. {  
  5.     int                events;  
  6.     uint32_t           revents;  
  7.     ngx_int_t          instance, i;  
  8.     ngx_uint_t         level;  
  9.     ngx_err_t          err;  
  10.     ngx_event_t       *rev, *wev, **queue;  
  11.     ngx_connection_t  *c;  
  12.   
  13.     /* NGX_TIMER_INFINITE == INFTIM */  
  14.   
  15.     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,  
  16.                    "epoll timer: %M", timer);  
  17.   
  18. //这里是epoll的wait,将得到的事件存到event_list里面,最大的事件量是nevents  
  19.     /*一开始就是等待事件,最长等待时间为timer;nginx为事件 
  20.     专门用红黑树维护了一个计时器。后续对这个timer单独分析。 
  21.     */  
  22.     events = epoll_wait(ep, event_list, (int) nevents, timer);    //这个超时事件是从红黑树里面获取的,当前最近的超时,这样可以保证epoll的wait能够在合适的时间内返回,保证定义的超时事件可以执行  
  23.   
  24.     err = (events == -1) ? ngx_errno : 0;  
  25.   
  26.     if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {  
  27.         ngx_time_update();  
  28.     }  
  29.   
  30.     if (err) {  
  31.         if (err == NGX_EINTR) {  
  32.   
  33.             if (ngx_event_timer_alarm) {  
  34.                 ngx_event_timer_alarm = 0;  
  35.                 return NGX_OK;  
  36.             }  
  37.   
  38.             level = NGX_LOG_INFO;  
  39.   
  40.         } else {  
  41.             level = NGX_LOG_ALERT;  
  42.         }  
  43.   
  44.         ngx_log_error(level, cycle->log, err, "epoll_wait() failed");  
  45.         return NGX_ERROR;  
  46.     }  
  47.   
  48.     if (events == 0) {  
  49.         if (timer != NGX_TIMER_INFINITE) {  
  50.             return NGX_OK;  
  51.         }  
  52.   
  53.         ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,  
  54.                       "epoll_wait() returned no events without timeout");  
  55.         return NGX_ERROR;  
  56.     }  
  57. //这个锁貌似是线程的时候用吧。。没有搞懂  
  58.     ngx_mutex_lock(ngx_posted_events_mutex);  
  59.   
  60. //循环遍历所有产生的事件  
  61.     for (i = 0; i < events; i++) {  
  62.         c = event_list[i].data.ptr;  //获取该事件实际对应的connection  
  63.   
  64. //instance 说白了就是个整形的变量  
  65.         instance = (uintptr_t) c & 1;  
  66.         c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);  
  67.   
  68.         rev = c->read;  
  69.   
  70.         if (c->fd == -1 || rev->instance != instance) {  
  71.   
  72.             /* 
  73.              * the stale event from a file descriptor 
  74.              * that was just closed in this iteration 
  75.              */  
  76.   
  77.             ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,  
  78.                            "epoll: stale event %p", c);  
  79.             continue;  
  80.         }  
  81.     //获取发生的事件的类型  
  82.         revents = event_list[i].events;  
  83.   
  84.         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,  
  85.                        "epoll: fd:%d ev:%04XD d:%p",  
  86.                        c->fd, revents, event_list[i].data.ptr);  
  87. //如果发生了错误事件  
  88.         if (revents & (EPOLLERR|EPOLLHUP)) {  
  89.             ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,  
  90.                            "epoll_wait() error on fd:%d ev:%04XD",  
  91.                            c->fd, revents);  
  92.         }  
  93.   
  94. #if 0  
  95.         if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {  
  96.             ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,  
  97.                           "strange epoll_wait() events fd:%d ev:%04XD",  
  98.                           c->fd, revents);  
  99.         }  
  100. #endif  
  101.   
  102.         if ((revents & (EPOLLERR|EPOLLHUP))  
  103.              && (revents & (EPOLLIN|EPOLLOUT)) == 0)  
  104.         {  
  105.             /* 
  106.              * if the error events were returned without EPOLLIN or EPOLLOUT, 
  107.              * then add these flags to handle the events at least in one 
  108.              * active handler 
  109.              */  
  110.   
  111.             revents |= EPOLLIN|EPOLLOUT;  
  112.         }  
  113.         /*该事件是一个读事件,并该连接上注册的读事件是active的*/  
  114.         if ((revents & EPOLLIN) && rev->active) {  
  115.   
  116.             if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {  
  117.                 rev->posted_ready = 1;  
  118.   
  119.             } else {  
  120.                 rev->ready = 1;  
  121.             }  
  122.   
  123.             if (flags & NGX_POST_EVENTS) {  
  124. //如果设置了NGX_POST_EVENTS,表示当前worker进程已经获取了锁,那么将获取的事件入队,因为可能是监听端口的accept事件,这里如果是监听端口的accept事件的话,那么该event的accept域会置为1 ,这个是在事件模块的worker进程初始化中会设置的  
  125. //这里持有了锁就应该将产生的事件放入队列中,是为了能够在锁释放了以后再处理这些事件,这样可以让别的worker进程能够尽快的获取锁  
  126.                 queue = (ngx_event_t **) (rev->accept ?  
  127.                                &ngx_posted_accept_events : &ngx_posted_events);  
  128.   
  129.                 ngx_locked_post_event(rev, queue);  
  130.   
  131.             } else {  
  132.                 rev->handler(rev);  
  133.             }  
  134.         }  
  135.   
  136.         wev = c->write;  
  137. //如果是写事件,而且相应connection的写事件是激活的  
  138.         if ((revents & EPOLLOUT) && wev->active) {  
  139.   
  140.             if (c->fd == -1 || wev->instance != instance) {  
  141.   
  142.                 /* 
  143.                  * the stale event from a file descriptor 
  144.                  * that was just closed in this iteration 
  145.                  */  
  146.   
  147.                 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,  
  148.                                "epoll: stale event %p", c);  
  149.                 continue;  
  150.             }  
  151.   
  152.             if (flags & NGX_POST_THREAD_EVENTS) {  
  153.                 wev->posted_ready = 1;  
  154.   
  155.             } else {  
  156.                 wev->ready = 1;  
  157.             }  
  158.   
  159.             if (flags & NGX_POST_EVENTS) {  
  160.                 ngx_locked_post_event(wev, &ngx_posted_events);  
  161.   
  162.             } else {  
  163.                 wev->handler(wev);  
  164.             }  
  165.         }  
  166.     }  
  167.   
  168.     ngx_mutex_unlock(ngx_posted_events_mutex);  
  169.   
  170.     return NGX_OK;  
  171. }  
上述代码还是非常简单的,说白了就是调用epoll的wait函数,获取epoll的event,接着根据其的data的ptr域找到所属的connection,然后根据epoll的事件类型来对应connection中的read与write事件,并将其加入到事件队列中,待会再调用其的处理函数来处理。。。

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

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值