六、nginx的事件监听(参考《深入剖析Nginx》)(上)

 1、在第四章(nginx启动过程中的进程创建)中,提到子进程最后在for循环中进行事件处理,其相关代码具体定义为:

    static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
ngx_uint_t         i;
ngx_connection_t  *c;
ngx_process = NGX_PROCESS_WORKER;
ngx_worker_process_init(cycle, 1);

ngx_setproctitle("worker process");
//...

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

 在for循环中,不断执行ngx_process_events_and_timers来处理事件。在具体查看该函数定义之前,先了解一下其参数cycle的定义。

    struct ngx_cycle_s {
void                  ****conf_ctx;
ngx_pool_t               *pool;

ngx_log_t                *log;
ngx_log_t                 new_log;

ngx_connection_t        **files;
ngx_connection_t         *free_connections;
ngx_uint_t                free_connection_n;

ngx_queue_t               reusable_connections_queue;

ngx_array_t               listening;
ngx_array_t               pathes;
ngx_list_t                open_files;
ngx_list_t                shared_memory;

ngx_uint_t                connection_n;
ngx_uint_t                files_n;

ngx_connection_t         *connections;
ngx_event_t              *read_events;
ngx_event_t              *write_events;

ngx_cycle_t              *old_cycle;

ngx_str_t                 conf_file;
ngx_str_t                 conf_param;
ngx_str_t                 conf_prefix;
ngx_str_t                 prefix;
ngx_str_t                 lock_file;
ngx_str_t                 hostname;
};

 关于cycle的具体内涵,请查看nginx 源码学习笔记(十四)—— 全局变量ngx_cycle,在此不多做赘述。
  2、关于ngx_process_events_and_timers
 该函数位于ngx_event.c,其具体定义为:

      void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ngx_uint_t  flags;
    ngx_msec_t  timer, delta;

    if (ngx_timer_resolution) {
    //...
}
    //多进程竞争端口,所以加锁
    if (ngx_use_accept_mutex) {
   //...
}
     //delta用于计算时间
    delta = ngx_current_msec;
    //进行事件处理
    (void) ngx_process_events(cycle, timer, flags);
    
    delta = ngx_current_msec - delta;

      //处理accept事件
     if (ngx_posted_accept_events) {
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);
}
    //对ngx_use_accept_mutex解锁
    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
 }

    if (delta) {
        ngx_event_expire_timers();
}

    if (ngx_posted_events) {
      //...
}

  3、先关注一下ngx_process_events_and_timers的逻辑结构(简单来看),在多进程下,如下如所示:


image.png

  4、下面关注事件处理的函数ngx_process_events,发现它本身只是一个宏定义:

#define ngx_process_events   ngx_event_actions.process_events

 而 ngx_event_actions是ngx_event.c中的一个结构体:

    ngx_event_actions_t   ngx_event_actions;

 显然,关键在于结构体ngx_event_actions_t 的定义和初始化。先看看定义吧:

    typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    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);

    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
    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;

 这个结构体中定义了大量的函数指针。上文的ngx_process_events_and_timers调用的应该是其中的

    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
               ngx_uint_t flags);

 那么问题来了,这些函数指针指向何处呢?

 5、nginx事件模块的初始化(略)
 在ngx_process_cycle.c中,有如下代码用于初始化各个模块:

    for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->init_process) {
        if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
            /* fatal */
            exit(2);
        }
    }
}

 而ngx_modules[i]->init_process也是函数指针,关于全局变量ngx_modules的初始化,我还未完全理解。但可以确定的是,若在conf文件中配置使用epoll作为IO多路复用的机制,则这里的ngx_event_actions_t结构体中的process_events函数将会绑定为epoll模块中的ngx_epoll_process_events函数。

 6、总结一下,至此,已经知道worker进程在循环中不断调用ngx_process_events_and _timers函数,若使用epoll,则该函数对应ngx_epoll_process_events。这个函数的主要作用在于把获取到的事件放进对应的事件队列,留待后续函数处理。其主要代码为:

    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;


        events = epoll_wait(ep, event_list, (int) nevents, timer);

      //...

if (events == 0) {
 //...
}

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) {
    //...
    }
    revents = event_list[i].events;
            //...


    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) {
            //...
            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;
}

 重点在于:

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

 若事件需要延后处理,则会将其放进队列中,留待ngx_event_process_posted等函数处理。若不是,则直接调用handler进行处理。

 7、限于时间,本人还未读懂事件处理的主要逻辑,所以本篇写得不甚清楚#=_=。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343