模块的初始化
Nginx在编译的时候会根据默认配置和我们指定的编译选项生成一个ngx_modules.c
文件,这个文件包含了所有要编译进去的模块。
Nginx在启动时会调用main
->ngx_init_cycle
->ngx_init_modules
函数来调用每个模块的init_module
函数初始化所有模块,
init_module
函数是用于模块级的初始化,实际上大多数模块都没有实现init_module
函数:
ngx_int_t
ngx_init_modules(ngx_cycle_t *cycle)
{
ngx_uint_t i;
/* 遍历所有模块,调用每个模块的init_module函数完成初始化工作 */
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->init_module) {
if (cycle->modules[i]->init_module(cycle) != NGX_OK) {
return NGX_ERROR;
}
}
}
return NGX_OK;
}
上一篇文章中提到work进程最终会进入ngx_worker_process_cycle
函数,在这个函数里面首先会调用ngx_worker_process_init
函数完成模块的进程级初始化,然后进入死循环等待事件处理。
在ngx_worker_process_init
函数中会循环调用所有模块的init_process
函数进行模块的进程级初始化:
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->init_process) {
if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
/* fatal */
exit(2);
}
}
}
EVENT模块事件初始化
首先来看EVENT核心模块对网络事件的初始化,EVENT模块的初始化主要是在init_process
函数中进行的,EVENT模块实现的init_process
函数是ngx_event_process_init
函数。
此函数的主要作用是初始化连接池,然后将监听套接字的事件处理函数挂载到事件循环中去,对于TCP来说当有请求到来时,调用ngx_event_accept
函数来接收请求。
/* 设置监听套接字的事件处理函数为 ngx_event_accept */
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
#if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport) {
/* 将监听套接字挂载到事件循环中去 */
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
continue;
}
#endif
下面来看epoll的事件处理函数ngx_epoll_process_events
,epoll_wait等待内核唤醒处理事件,如果有可读事件则调用rev->handler函数来处理,
如果有可写事件则调用wev->handler来处理:
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
···
/* 首先epoll_wait进入内核等待事件唤醒 */
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;
···
/* 处理可读事件 */
if ((revents & EPOLLIN) && rev->active) {
rev->ready = 1;
if (flags & NGX_POST_EVENTS) {
queue = rev->accept ? &ngx_posted_accept_events
: &ngx_posted_events;
ngx_post_event(rev, queue);
} else {
/* 调用对应套接字的可读事件处理函数 */
rev->handler(rev);
}
}
wev = c->write;
/* 处理可写事件 */
if ((revents & EPOLLOUT) && wev->active) {
if (c->fd == -1 || wev->instance != instance) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: stale event %p", c);
continue;
}
wev->ready = 1;
if (flags & NGX_POST_EVENTS) {
ngx_post_event(wev, &ngx_posted_events);
} else {
/* 调用对应套接字的可写事件处理函数 */
wev->handler(wev);
}
}
}
return NGX_OK;
}
对于监听套接字来说,有可读事件就会进入ngx_event_accept
函数来接收请求。ngx_event_accept
函数首先调用accept
函数接收TCP套接字请求,
对接收到的新套接字创建一个ngx_connection_t
连接并初始化,然后调用注册进监听套接字ngx_listening_t
结构体中的handler
函数进行下一步处理。
对于HTTP请求来说这个handler
函数由HTTP模块注册,下面会讲到。
HTTP模块请求初始化
再来看HTTP核心模块对HTTP请求的初始化,
HTTP模块是在nginx启动阶段读取配置时调用HTTP模块的配置解析函数ngx_http_block
进行初始化的,调用链如下:main
->ngx_init_cycle
->ngx_conf_parse
->ngx_conf_handler
,
在ngx_conf_parse
函数中读取并解析配置,然后调用ngx_conf_handler
函数来查找此配置是属于哪个模块的,找到后调用对应模块注册时各自实现的set
函数来处理配置。
对于HTTP模块来说它注册的set
函数是ngx_http_block
,所以当遇到http
这个配置项时就会调用ngx_http_block
来解析配置并初始化。
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
};
ngx_http_block
函数的作用主要是解析http
块里面的配置项,然后注册HTTP请求的处理函数,注册调用链如下:ngx_http_block
->ngx_http_optimize_servers
->ngx_http_init_listening
->ngx_http_add_listening
在ngx_http_add_listening
函数中会新建一个ngx_listening_t
结构体并初始化,其中的handler
函数设置为ngx_http_init_connection
。
所以上一节提到接收TCP请求之后调用handler
函数,就会进入ngx_http_init_connection
函数中去。
ngx_http_init_connection
函数的作用主要是将新接收的套接字读事件处理函数设置为ngx_http_wait_request_handler
,然后设置超时时间,最后将套接字加入到event事件循环中。
当发送HTTP请求时就会直接调用ngx_http_wait_request_handler
函数来处理HTTP请求头。
void
ngx_http_init_connection(ngx_connection_t *c)
{
···
/* the default server configuration for the address:port */
hc->conf_ctx = hc->addr_conf->default_server->ctx;
ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
if (ctx == NULL) {
ngx_http_close_connection(c);
return;
}
ctx->connection = c;
ctx->request = NULL;
ctx->current_request = NULL;
c->log->connection = c->number;
c->log->handler = ngx_http_log_error;
c->log->data = ctx;
c->log->action = "waiting for request";
c->log_error = NGX_ERROR_INFO;
rev = c->read;
/* 设置读事件处理函数 */
rev->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
if (hc->addr_conf->proxy_protocol) {
hc->proxy_protocol = 1;
c->log->action = "reading PROXY protocol";
}
if (rev->ready) {
/* the deferred accept(), iocp */
if (ngx_use_accept_mutex) {
ngx_post_event(rev, &ngx_posted_events);
return;
}
rev->handler(rev);
return;
}
/* 设置超时时间 */
ngx_add_timer(rev, c->listening->post_accept_timeout);
ngx_reusable_connection(c, 1);
/* 将套接字添加到event事件循环中 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
总结
至此Nginx的网络请求处理流程就基本梳理清楚了,接下来分析HTTP请求的具体处理过程。
上面的分析使用的是epoll模型,并且开启了port_reuse选项。
Nginx网络请求的处理流程大概是:
- Nginx的master进程根据work进程的数量N,创建N个监听套接字。
- event事件模块将监听套接字加入到epoll事件中,并设置事件处理函数为
ngx_event_accept
函数。 - 当有请求到来时,epoll事件处理函数直接调用
ngx_event_accept
函数来接收请求并调用HTTP模块注册的处理函数ngx_http_init_connection
。 ngx_http_init_connection
函数将接收到的新套接字加入到epoll事件中,并设置事件处理函数为ngx_http_wait_request_handler
函数。- 此时TCP连接建立完成,当发送HTTP请求时,epoll事件处理函数直接调用
ngx_http_wait_request_handler
函数来处理请求。
这里还有一个疑问,work进程是怎么进入到epoll事件处理函数的?
work进程在ngx_worker_process_cycle
函数中无限循环调用ngx_process_events_and_timers
函数,此函数会调用ngx_process_events
函数,此函数就是epoll模块注册的事件处理函数ngx_epoll_process_events
。
基于nginx-1.14.0源码分析