nginx 事件模块简单剖析

我们这里以单进程启动为例

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框架处理流程

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

推荐阅读更多精彩内容