资讯专栏INFORMATION COLUMN

nginx 事件模块简单剖析

huayeluoliuhen / 1266人阅读

摘要:我们这里以单进程启动为例中的函数调用这个函数回循环调用中的事件循环的核心函数是。这个方法仅在方法中调用,它是处理,分发事件的核心初始化事件驱动模块的方法退出事件驱动模块前调用的方法。读事件的回调函数是这样就进入了框架处理流程

我们这里以单进程启动为例
nginx.c中的main 函数调用ngx_single_process_cycle

这个函数回循环调用

ngx_process_cycle.c 中的

for ( ;; ) {
  ....
  ngx_process_events_and_timers
  ....
}

事件循环的核心函数是 ngx_process_events_and_timers 。这个函数主要干了四件 事情:抢占 accept mutex,等待并分发事件,处理 accept 事件,处理其他io事件

我们这里只介绍等待分发事件
ngx_event.c 中的

    (void) ngx_process_events(cycle, timer, flags);

这里开始 wait并分发事件, 我们来可以来看一下这个函数
可以看到在 ngx_event.h 中的一个宏

#define ngx_process_events   ngx_event_actions.process_events

我们来看一下 ngx_event_actions 这个数据结构

typedef struct {
    /*
    添加事件方法,它将负责把1个感兴趣的事件添加到操作系统提供的事件驱动机制(如epoll,kqueue等)中,
    这样,在事件发生之后,将可以在调用下面的process_envets时获取这个事件。
    */
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*
    删除事件方法,它将一个已经存在于事件驱动机制中的事件一出,这样以后即使这个事件发生,调用process_events方法时也无法再获取这个事件
    */
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    /*
    启用一个事件,目前事件框架不会调用这个方法,大部分事件驱动模块对于该方法的实现都是与上面的add方法完全一致的
    */
    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*
    禁用一个事件,目前事件框架不会调用这个方法,大部分事件驱动模块对于该方法的实现都是与上面的del方法一致
    */
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    /*
    向事件驱动机制中添加一个新的连接,这意味着连接上的读写事件都添加到事件驱动机制中了
    */
    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    // 从事件驱动机制中一出一个连续的读写事件
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    // 仅在多线程环境下会被调用,目前,nginx在产品环境下还不会以多线程方式运行。
    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
    // 在正常的工作循环中,将通过调用process_events方法来处理事件。
    // 这个方法仅在ngx_process_events_and_timers方法中调用,它是处理,分发事件的核心
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                   ngx_uint_t flags);

    // 初始化事件驱动模块的方法
    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    // 退出事件驱动模块前调用的方法。
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;


extern ngx_event_actions_t   ngx_event_actions;

这个数据结构中定义了很多函数指针,

这里我们的配置是

events { 
    use epoll; 
    worker_connections 1024;     #所以nginx支持的总连接数就等于worker_processes * worker_connections 
} 

使用的是 epoll 事件模块,在epoll 模块初始化的时候调用

ngx_epoll_module.c 中的ngx_epoll_init的函数

其中给ngx_event_actions赋值

ngx_event_actions = ngx_epoll_module_ctx.actions 

我们来看下 ngx_epoll_module_ctx
结构类型是


typedef struct { // 事件模块的名称 ngx_str_t *name; // 在解析配置项前,这个回调方法用于创建存储配置项参数的结构体 void *(*create_conf)(ngx_cycle_t *cycle); // 在解析配置项完成后,init_conf方法会被调用,用于综合处理当前事件模块感兴趣的全部配置项。 char *(*init_conf)(ngx_cycle_t *cycle, void *conf); // 对于事件驱动机制,每个事件模块需要实现的10个抽象方法 ngx_event_actions_t actions; } ngx_event_module_t;

初始化

//epoll是个event模块
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 */
    }
};


这些事件处理函数都在 ngx_epoll_module.c 这个文件中,大家可以看一下源码

综上,根据我们的配置, ngx_event.c 中的 ngx_process_events
实际调用的是 ngx_epoll_module.c 中的 ngx_epoll_process_events

这个函数有点长,我们找些关键的点看一下,

 //一开始就是等待事件,最长等待时间为timer;nginx为事件专门用红黑树维护了一个计时器
    events = epoll_wait(ep, event_list, (int) nevents, timer);

所有收集到的事件都放在了event_list 中,我们来看一下这个event_list

static struct epoll_event  *event_list;

struct epoll_event {
    uint32_t      events;
    epoll_data_t  data;
};


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


下面就开始对这些事件就行处理, 先加锁,

 ngx_mutex_lock(ngx_posted_events_mutex);
    //循环开始处理收到的所有事件
    for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;

        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);
        //记录wait的错误返回状态
        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
        //该事件是一个读事件,并该连接上注册的读事件是active的
        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;
        }

        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) {
                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;

        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);

先加锁,对event_list 中的事件循环处理,
在每个循环中,
先获取这个事件所在的连接, 然后判断是读事件还是写事件, 然后调用注册在这个事件上的的回调函数。 读事件的回调函数是
ngx_http_init_connection 这样就进入了 HTTP框架处理流程

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39136.html

相关文章

  • nginx架构

    摘要:反向代理反向代理反向代理负载均衡鉴权限流等逻辑架构在逻辑上分为入口层,模块化的功能处理层,系统调用层。多个共同监听事件并处理,反向代理会把请求转发给后端服务。 一.概述 本文将深入剖析nginx的架构。 第一部分介绍nginx现有框架,用典型的4+1视图阐述,包括逻辑架构,开发架构,运行架构,物理架构,功能用例,nginx为单机服务,不考虑物理架构。其中功能用例概述nginx功能;逻辑...

    smartlion 评论0 收藏0
  • nginx架构

    摘要:反向代理反向代理反向代理负载均衡鉴权限流等逻辑架构在逻辑上分为入口层,模块化的功能处理层,系统调用层。多个共同监听事件并处理,反向代理会把请求转发给后端服务。 一.概述 本文将深入剖析nginx的架构。 第一部分介绍nginx现有框架,用典型的4+1视图阐述,包括逻辑架构,开发架构,运行架构,物理架构,功能用例,nginx为单机服务,不考虑物理架构。其中功能用例概述nginx功能;逻辑...

    fuyi501 评论0 收藏0
  • Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)

    摘要:和服务关系最密切的进程是中的进程组,绝大部分业务处理都在该进程中进行。随后触发一个事件各组件通过该事件进行配置文件加载路由注册。事件每个请求到来时仅仅会触发事件。服务器生命周期和服务基本一致,详情参考源码剖析功能实现 作者:bromine链接:https://www.jianshu.com/p/4c0...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。S...

    张汉庆 评论0 收藏0
  • epoll LT/ET 深入剖析

    摘要:深入剖析事件有两种模型水平触发接收缓冲区不为空有数据可读读事件一直触发发送缓冲区不满可以继续写入数据写事件一直触发符合思维习惯,返回的事件就是的状态边沿触发的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件的发送缓冲 epoll LT/ET 深入剖析 EPOLL事件有两种模型: Level Triggered (LT) 水平触发.socket接收缓冲区不为空 有...

    microelec 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<