事件驱动是nginx设计的核心,linux平台下,nginx会优先使用epoll进行事件处理。main—>master_process_cycle—>ngx_start_worker_process—>ngx_worker_process_cycle—>ngx_worker_process_init,ngx_process_events_and_timers。master进程中创建worker进程,之后在ngx_worker_process_cycle函数中初始化,并打开socketpair,加入epoll中。随后进入ngx_process_events_and_timers函数,等待http连接请求。nginx作为web服务器,性能要求很高,对最耗时的IO进行异步处理。注册到epoll上,作为一个个事件待触发。定义的事件处理函数如下:
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 */
}
};
worker进程启动后调用ngx_worker_process_init,初始化连接,监听80端口,之后不断调用ngx_process_events_and_timers 函数,等待http连接请求。
ngx_process_events_and_timers
if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
//多进程情况下通过共享内存的互斥锁,同一时间只允许一个进程得到访问,解决惊群问题。
} else {
//得到互斥锁后,将对应端口的(Http默认为80端口)事件加入epoll,等待客户端连接请求,触发IO事件。
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
//多进程情况下,使用NGX_POST_EVENTS,将触发的事件放入队列中,而不是里面触发IO的回调函数
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
//调用的是ngx_epoll_process_events,执行的是epoll_wait,当有事件被触发时,将执行IO回调函数,或者将被触发的IO事件添加到链表。
(void) ngx_process_events(cycle, timer, flags);
//依次访问被触发的IO事件,执行回调
if (ngx_posted_accept_events) {
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
}
//释放共享互斥锁
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
//处理超时事件
if (delta) {
ngx_event_expire_timers();
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"posted events %p", ngx_posted_events);
//处理被触发的IO事件回调
if (ngx_posted_events) {
if (ngx_threaded) {
ngx_wakeup_worker_thread(cycle);
} else {
ngx_event_process_posted(cycle, &ngx_posted_events);
}
}
之所以要在多进程时,将触发的IO事件先添加链表,而不是立马执行回调。是因为ngx_trylock_accept_mutex函数会抢占锁,这样其他进程就必须等待,直到锁被释放才可以继续接受事件触发。而一般的IO回调函数都比较耗时,一旦多个事件同时触发,等待周期会比较长。
(void) ngx_process_events(cycle, timer, flags)
events = epoll_wait(ep, event_list, (int) nevents, timer);
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;
//多进程情况下不执行handler,而是添加到链表中
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);
}
}
当Http客户端发来连接请求后,触发读事件的Handler函数,在初始化时,设定这个函数为ngx_event_accept。这个函数里调用accept,建立TCP通信。
ngx_event_accept
lc = ev->data;
ls = lc->listening;
ev->ready = 0;
do {
socklen = NGX_SOCKADDRLEN;
//accept等待连接建立
s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
//当一个进程的free连接数占连接总数的八分之一时,不在接收新的连接请求
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
//获取一个可用的连接,并初始化
c = ngx_get_connection(s, ev->log);
c->pool = ngx_create_pool(ls->pool_size, ev->log);
if (c->pool == NULL) {
ngx_close_accepted_connection(c);
return;
}
c->sockaddr = ngx_palloc(c->pool, socklen);
if (c->sockaddr == NULL) {
ngx_close_accepted_connection(c);
return;
}
ngx_memcpy(c->sockaddr, sa, socklen);
log = ngx_palloc(c->pool, sizeof(ngx_log_t));
if (log == NULL) {
ngx_close_accepted_connection(c);
return;
}
*log = ls->log;
//该Http连接所用的recv、send函数
c->recv = ngx_recv;
c->send = ngx_send;
c->recv_chain = ngx_recv_chain;
c->send_chain = ngx_send_chain;
c->log = log;
c->pool->log = log;
c->socklen = socklen;
c->listening = ls;
c->local_sockaddr = ls->sockaddr;
c->local_socklen = ls->socklen;
c->unexpected_eof = 1;
}
rev = c->read;
wev = c->write;
wev->ready = 1;
if (ev->deferred_accept) {
rev->ready = 1;
}
rev->log = log;
wev->log = log;
/*
* TODO: MT: - ngx_atomic_fetch_add()
* or protection by critical section or light mutex
*
* TODO: MP: - allocated in a shared memory
* - ngx_atomic_fetch_add()
* or protection by critical section or light mutex
*/
//nginx的http连接数加1,连接数也是一个共享内存的全局变量
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
//将新的connection加入到epoll中
if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
if (ngx_add_conn(c) == NGX_ERROR) {
ngx_close_accepted_connection(c);
return;
}
}
log->data = NULL;
log->handler = NULL;
//调用监听套接字的回调函数,这里是ngx_http_init_connection
ls->handler(c);
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
ev->available--;
}
} while (ev->available);
}
listener的回调函数是http模块在初始化的时候被赋值的。
static ngx_command_t ngx_http_commands[] = {
{ ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
0,
0,
NULL },
ngx_null_command
};
在Http模块被加载的时候,会调用模块初始化函数,ngx_http_block—>ngx_http_optimize_servers—>ngx_http_init_listening—>ngx_http_add_listening。在ngx_http_add_listening中创建一个监听结构体ngx_listening_t,并初始化,设置回调句柄ls->handler = ngx_http_init_connection。
ngx_http_init_connection函数中又进一步设定connection读事件的回调函数为ngx_http_wait_request_handler,写事件的回调函数是ngx_http_empty_handler。并将读事件加入epoll监听队列,等待客户端的http请求报文。
ngx_http_wait_request_handler函数分配内存,然后接收报文,并调用ngx_http_process_request_line解析报文的每一行信息,进行Http处理的每个环节。ngx_http_process_request_line—>ngx_http_handler—>ngx_http_core_run_phases。然后执行各个phases函数。
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
size_t size;
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
c = rev->data;
//连接响应超时,关闭连接
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
ngx_http_close_connection(c);
return;
}
if (c->close) {
ngx_http_close_connection(c);
return;
}
hc = c->data;
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
size = cscf->client_header_buffer_size;
b = c->buffer;
//开辟一块缓存,用来接收Http报文
if (b == NULL) {
b = ngx_create_temp_buf(c->pool, size);
if (b == NULL) {
ngx_http_close_connection(c);
return;
}
c->buffer = b;
} else if (b->start == NULL) {
b->start = ngx_palloc(c->pool, size);
if (b->start == NULL) {
ngx_http_close_connection(c);
return;
}
b->pos = b->start;
b->last = b->start;
b->end = b->last + size;
}
//从网络缓存里读出http报文
n = c->recv(c, b->last, size);
if (n == NGX_ERROR) {
ngx_http_close_connection(c);
return;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client closed connection");
ngx_http_close_connection(c);
return;
}
b->last += n;
c->log->action = "reading client request line";
ngx_reusable_connection(c, 0);
c->data = ngx_http_create_request(c);
if (c->data == NULL) {
ngx_http_close_connection(c);
return;
}
//这里将读事件的回调句柄改为ngx_http_process_request_line,是因为一个
//http请求的报文可能会很长,可能会超出TCP的接收buffer,这样就会多次触发读
//事件
rev->handler = ngx_http_process_request_line;
ngx_http_process_request_line(rev);
}
很多基于nginx的二次开发都是通过在Http报文里加入一些属性,然后开发对应的phrase方法,将phrase方法加入ngx_http_core_commands中。
static ngx_command_t ngx_http_core_commands[] = {
{ ngx_string("variables_hash_max_size"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_core_main_conf_t, variables_hash_max_size),
NULL },
{ ngx_string("variables_hash_bucket_size"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_core_main_conf_t, variables_hash_bucket_size),
NULL },
//... ...
客户端关闭http连接,调用ngx_http_finalize_request—>ngx_http_finalize_connection—>ngx_http_close_request—>ngx_http_free_request,ngx_http_close_connection。释放内存,关闭套接字,销毁内存池。