• Nginx:事件模块


    参考资料<深入理解Nginx>

    根据不同的系统内核,Nginx会使用不同的事件驱动机制,本次描述的场景是使用epoll来驱动事件的处理。

    epoll的使用方法

    1.int epoll_create(int size);

    epoll_create返回一个句柄,之后epoll的使用将依靠这个句柄来标识。参数size只是告诉epoll所要处理的大致事件数目,一些内核版本的实现中,这个参数没有任何意义。

    2.int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

    epoll_ctl向epoll对象中添加、修改或者删除感兴趣的事件,返回0表示成功,否则返回-1。

    参数epfd是epoll_create方法返回的句柄,而op参数的意义如下表

    参数fd是待检测的连接套接字,第四个参数是在告诉epoll对什么样的事件感兴趣,它使用的epoll_event结构体定义如下

    struct epoll_event {
        _uint32_t events;
        epoll_data_t data;
    };

       events取值如下表

      而data成员是一个epoll_data联合

    typedef union epoll_data {
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;

    这个data成员还与具体的使用方式相关。例如,ngx_epoll_module模块使用了给ptr成员,作为指向ngx_connection_t连接的指针。

    3.int epoll_wait(int fd,struct epoll_event *events,int maxevents,int timeout);

       第一个参数epfd是epoll的描述符。第二个参数events则是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中,

       第三个参数表示本次可以返回的最大事件数目,第四个参数表示在没有检测到时间发生时最多等待的时间。

    ngx_epoll_module模块

    该模块使用如下的上下文结构

    ngx_event_module_t  ngx_epoll_module_ctx = {
        &epoll_name,
        ngx_epoll_create_conf,               /* create configuration */
        ngx_epoll_init_conf,                 /* init configuration */
    
        {
            ngx_epoll_add_event,             /* add an event */
            ngx_epoll_del_event,             /* delete an event */
            ngx_epoll_add_event,             /* enable an event */
            ngx_epoll_del_event,             /* disable an event */
            ngx_epoll_add_connection,        /* add an connection */
            ngx_epoll_del_connection,        /* delete an connection */
            NULL,                            /* process the changes */
            ngx_epoll_process_events,        /* process the events */
            ngx_epoll_init,                  /* init the events */
            ngx_epoll_done,                  /* done the events */
        }
    };

    在Nginx的启动过程中。ngx_epoll_init方法将会被调用,它主要做了两件事情:

    1.调用epoll_create方法创建epoll对象。

    2.创建event_list数组,用于进行epoll_wait调用时传递内核态的事件。

    ngx_epoll_add_event通过调用epoll_ctl向epoll中添加或者删除事件。可以通过这个函数的部分源码来了解一下该过程

     1 static ngx_int_t
     2 ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
     3 {
     4     int                  op;
     5     uint32_t             events, prev;
     6     ngx_event_t         *e;
     7     ngx_connection_t    *c;
     8     struct epoll_event   ee;
     9 
    10     //每个事件的data成员都存放着其对应的ngx_connection_t连接
    11     c = ev->data;
    12 
    13     //确定当前时间是读事件还是写事件
    14     events = (uint32_t) event;
    15 
    16     ...
    17     //根据active标志位确定是否为活跃事件,以决定到底是修改还是添加事件
    18     if (e->active) {
    19         op = EPOLL_CTL_MOD;
    20         events |= prev;
    21 
    22     } else {
    23         op = EPOLL_CTL_ADD;
    24     }
    25 
    26     ee.events = events | (uint32_t) flags;
    27     ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
    28 
    29     //调用epoll_ctl方法向epoll中添加事件
    30     if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
    31         ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
    32                       "epoll_ctl(%d, %d) failed", op, c->fd);
    33         return NGX_ERROR;
    34     }
    35 
    36     //标识为活跃事件
    37     ev->active = 1;
    38 
    39     return NGX_OK;
    40 }
    View Code

    同理,ngx_epoll_del_event方法也通过类似的方式删除事件。

    对于ngx_epoll_add_connection和ngx_epoll_del_connection方法,也是调用epoll_ctl进行处理,只是每一个连接都对应读/写事件。

    ngx_epoll_process_events方法则调用epoll_wait来获取事件并且处理事件,下面是该函数的部分源码

     1 static ngx_int_t
     2 ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
     3 {
     4     int                       events;
     5     uint32_t               revents;
     6     ngx_int_t             instance, i;
     7     ngx_event_t         *rev, *wev, **queue;
     8     ngx_connection_t  *c;
     9 
    10 
    11     //一开始就是等待事件,最长等待时间为timer
    12     events = epoll_wait(ep, event_list, (int) nevents, timer);
    13     
    14     ...
    15     //循环开始处理收到的所有事件
    16     for (i = 0; i < events; i++) {
    17         //ptr成员指向的是ngx_connection_t,但最后一位有特殊含义,需要将它屏蔽掉
    18         c = event_list[i].data.ptr;
    19         instance = (uintptr_t) c & 1;
    20         c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
    21         
    22         //取出读事件
    23         rev = c->read;
    24         ...
    25         //取得发生一个事件
    26         revents = event_list[i].events;
    27 
    28         ...
    29         //该事件是一个读事件而且是活跃的
    30         if ((revents & EPOLLIN) && rev->active) {
    31 
    32             ...
    33             //如果设置了NGX_POST_EVENTS标识,事件放入到相应的队列中
    34             if (flags & NGX_POST_EVENTS) {
    35                 queue = (ngx_event_t **) (rev->accept ?
    36                                &ngx_posted_accept_events : &ngx_posted_events);
    37 
    38                 ngx_locked_post_event(rev, queue);
    39             //否则立刻处理
    40             } else {
    41                 rev->handler(rev);
    42             }
    43         }
    44       
    45         //获取写事件
    46         wev = c->write;
    47 
    48         if ((revents & EPOLLOUT) && wev->active) {
    49             
    50             ...
    51             if (flags & NGX_POST_EVENTS) {
    52                 ngx_locked_post_event(wev, &ngx_posted_events);
    53 
    54             } else {
    55                 wev->handler(wev);
    56             }
    57         }
    58     }
    59     ...
    60     return NGX_OK;
    61 }
    View Code

    ngx_process_events_and_timers流程

    在Nginx中,每个worker进程都在ngx_worker_process_cycle方法中循环处理事件。

    处理分发事件实际上就是调用的ngx_process_events_and_timers方法,循环调用该函数就是在处理所有的事件,该方法的核心的操作主要有以下3个:

    1.调用所使用的事件驱动模块实现的process_events方法,处理网络事件(调用ngx_epoll_process_events方法)。

    2.处理两个post事件队列中的事件(调用ngx_event_process_posted方法)。

    3.处理定时器时间(调用ngx_event_expire_timers方法)

    下图展示了该方法的流程,结合前面的进行理解

    事实上,在调用上面循环的方法之前ngx_event_core_module模块会做一些初始化工作:

    1.预分配cycle->connection数组充当连接池

    2.预分配所有写事件到cycle->read_events数组

    3.预分配所有读事件到cycle->write_events数组

    (Nginx中连接池跟读写事件都是这个阶段预先分配好的,其大小跟配置项有关)

    4.为所有ngx_listening_t监听对象中的connectin成员分配连接,同时对监听端口的读事件设置处理方法为ngx_event_accept(最后有这个方法的介绍)

    5.将监听对象连接的读事件添加到事件驱动模块中,这样,epoll等事件模块就开始检测监听服务,并开始向用户提供服务了。 

    建立新连接

    处理新连接事件的回调函数是ngx_event_accept,其原型如下

    void ngx_event_accept(ngx_event_t *ev);

    下图展示了该方法的流程

  • 相关阅读:
    10.26 饮食
    10.25 饮食
    10.24饮食
    关于 wpf 的ICommand 的 CanExecute CanExecuteChanged func action的认识
    2018 10 23
    [Java]先有Class还是先有Object?
    [Java]如何制作一个WordCount-Plus的Plugin
    [java] 软工实践WordCount-Plus
    [java]基本数据类型
    [java]第一个程序
  • 原文地址:https://www.cnblogs.com/runnyu/p/4914698.html
Copyright © 2020-2023  润新知