虽然我不想承认,但这篇文章的确是一篇很垃圾的博文。之所以垃圾 是因为没有考虑到Nginx的事件驱动对于请求处理的影响。建议各位看官去阅读
《http://tengine.taobao.org/book/index.html》和《深入理解Nginx--陶辉》的第11章内容。
这是我写的一篇关于Nginx中http请求处理的文章,里面参考了很多牛人的博客,由于本人当时疏忽,忘记了它们的网址,不能列举,还请见谅。
另由于本人能力有限,里面有很多地方肯定是不完善 或者是错误的,还请大家指正。
补充内容:
2.2phases[NGX_HTTP_LOG_PHASE + 1]的初始化...4
3.5 对于ngx_http_core_run_phases的详细解释...13
3.7 ngx_http_core_run_phases函数中的调用关系...16
附录四 ngx_http_core_run_phases完整代码...25
0序
本文分析Nginx内部是如何对http请求进行响应的。主要分为Nginx启动、有http请求到达nginx服务器两部分进行说明。之所以分为两部分,是因为在Nginx启动过程中会有很多初始化的过程。这些内容接下来都会有详细介绍。
1、基础知识
1.1 基本数据结构
1)以图的形式给出, 请见图1
2)关于图中 ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE +1]; 以及checker的初始化,在后面的内容中会给出详细解释。
3)关于图中结构体的原型,详见附录一
4)注意内容:
(1)这儿要明白的是ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];phase数组有NGX_HTTP_LOG_PHASE+1个ngx_http_phase_t元素。其中每个ngx_http_phase_t元素对应于8个phase。对于每个ngx_http_phase_t元素呢,又有一个相应的ngx_array_t存放相应的handler。
(2)经过相应初始化后,ngx_http_phase_tphases[NGX_HTTP_LOG_PHASE + 1];包含所有phase的所对应的handler,并通过相应的关联,会将ngx_http_phase_handler_sphase_engine中的ngx_http_handler_pt handler指向ngx_http_phase_tphases[NGX_HTTP_LOG_PHASE + 1]该数组,也就是以后使用phase_engine->handlers
时就代指ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE+ 1]该数组。
Figure 1 http请求中的典型结构体
1.2 http请求中phase的介绍
1) nginx中的处理一个http请求分成了11个phase。在下面这个enum枚举类型中,我们看到了这11个phase。(不知道为何有的文章写8个),关于这几个phase的详细说明,请见附录2.
typedef enum { NGX_HTTP_POST_READ_PHASE = 0, NGX_HTTP_SERVER_REWRITE_PHASE, NGX_HTTP_FIND_CONFIG_PHASE, NGX_HTTP_REWRITE_PHASE, NGX_HTTP_POST_REWRITE_PHASE, NGX_HTTP_PREACCESS_PHASE, NGX_HTTP_ACCESS_PHASE, NGX_HTTP_POST_ACCESS_PHASE, NGX_HTTP_TRY_FILES_PHASE, NGX_HTTP_CONTENT_PHASE, NGX_HTTP_LOG_PHASE } ngx_http_phases; |
2)更主要的是这几个phase的执行时严格按照顺序进行的,也就是NGX_HTTP_POST_READ_PHASE 是第一个,NGX_HTTP_LOG_PHASE是最后一个。
只有一个特殊的是NGX_HTTP_FIND_CONFIG_PHASE,因为在后面的rewrite phase中会改变uri,从而需要调用这个phase来实现通过uri来查找对应的location。
1.3 Nginx如何处理一个连接
1.3.1 基本介绍
在nginx中connection就是对tcp连接的封装,其中包括连接的socket,读事件,写事件。
nginx中对tcp连接的封装所用的结构体是structngx_connection_t结构体。
利用nginx封装的connection,我们可以很方便的使用nginx来处理与连接相关的事情,比如建立连接,发送与接收数据等。并且nginx中的http请求的处理就是建立在connection之上的。所以nginx不仅可以作为一个web服务器,也可以作为邮件服务器。当然,利用nginx提供的connection,我们可以与任何后端服务打交道。
1.3.2 Nginx如何处理一个连接
1)Nginx作为服务器
(1)启动
首先,nginx在启动时,会解析配置文件,得到需要监听的端口与ip地址
然后,nginx的master进程里面,先初始化好这个监控的socket(创建socket--设置addrreuse等选项--绑定到指定的ip地址端口--在listen),然后再fork出多个子进程出来,然后子进程会竞争accept新的连接。
此时,客户端就可以向nginx发起连接了。
(2)客户端向Nginx发起连接
首先:当客户端与nginx进行三次握手,与nginx建立好一个连接后,此时,某一个子进程会accept成功,得到这个建立好的socket,然后创建nginx对连接的封装,即ngx_connection_t结构体。
其次:设置读写事件处理函数并添加读写事件来与客户端进行数据的交互。
最后,nginx或客户端来主动关掉连接。至此,一个连接寿终正寝。
2)Nginx作为客户端
Nginx也可以作为客户端来请求其他server的数据(如upstream模块),此时与其他server创建连接,所创建的连接也封装在ngx_connection_t结构体中。
作为客户端,
首先:nginx获取一个ngx_connection_t结构体
然后,创建socket,并设置socket属性(比如非阻塞)
之后,通过添加读写事件,调用connect/read/write来调用连接
最后,关掉连接,释放ngx_connection_t。
2.Nginx启动过程中的初始化
2.1监听ip地址与端口的确定
step1:ngx_event_process_init
1)为每一个监听套接字分配一个连接结构ngx_connection_t
2)设置读事件成员(read)的事件处理函数为ngx_event_accept函数
如果没有accept互斥锁,ngx_event_process_init会将读事件挂载nginx的事件处理模型
如果有accept互斥锁,则会等到initprocess阶段结束,在工作进程的事件处理循环中,某个进程抢到了accept互斥锁,才能挂载该读事件。
step2:当一个工作进程在某个时刻将监听事件挂载上事件处理模型之后,nginx就开始正式接受并处理客户端的请求了。nginx的事件处理模型接收到这个读事件之后,会速度交由之前注册好的事件处理函数ngx_event_accept来处理。:
step3:ngx_event_accept函数中,nginx调用accept函数,从已连接队列得到一个连接以及对应的套接字,接着分配一个连接结构(ngx_connection_t),并将新得到的套接字保存在该连接结构中,这里还会做一些基本的连接初始化工作。其中包括初始化读写事件的处理函数。
写事件的处理函数设置为ngx_http_empty_handler。这个事件处理函数不会做任何操作,实际上nginx默认连接第一次可写,不会挂载写事件,如果有数据需要发送,nginx会直接写到这个连接,只有在发生一次写不完的情况下,才会挂载写事件到事件模型上,并设置真正的写事件处理函数,这里后面的章节还会做详细介绍读事件的处理函数设置为ngx_http_init_request。
此时如果该连接上已经有数据过来(设置了deferred accept),则会直接调用 ngx_http_init_request函数来处理该请求,反之则设置一个定时器并在事件处理模型上挂载一个读事件,等待数据到来或者超时。当然这里不管是已经有数据到来,或者需要等待数据到来,又或者等待超时,最终都会进入读事件的处理函数-ngx_http_init_request。
2.2 phases[NGX_HTTP_LOG_PHASE + 1]的初始化
接下来主要讲述ngx_http_core_main_conf_t中 ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];的初始化。
2.2.1理论说明:
1) 所有的http请求都会调用模块配置文件中的函数ngx_http_block函数
2) 在该函数中会调用postconfiguration的函数,实现对handler的初始化,相应的handler都会被存入相应phase[NGX_HTTP_XXX_PHASE]的handler数组中。
3) 从而完成对phases[NGX_HTTP_LOG_PHASE+ 1];的初始化。
2.2.2 详细说明:
1) 所有的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 };
|
2) 在该函数中会调用postconfiguration的函数,实现对handler的初始化,相应的handler都会被存入相应phase[NGX_HTTP_XXX_PHASE]的handler数组中。
/*this code is in ngx_http_block in ngx_http.c*/ for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_HTTP_MODULE) { continue; } sum_http_module++; /*tested by yankai*/ module = ngx_modules[m]-> ctx; if (module->postconfiguration ) { sum_post_configuration++;/*tested by yankai*/ if (module->postconfiguration (cf) != NGX_OK) { return NGX_CONF_ERROR; } } }
|
为了能够更好的说明handler是如何被初始化的,现在以ngx_http_access_module举例说明。
static ngx_http_module_t ngx_http_access_module_ctx = { NULL, /* preconfiguration */ ngx_http_access_init , /* postconfiguration */
NULL, /* create main configuration */ NULL, /* init main configuration */
NULL, /* create server configuration */ NULL, /* merge server configuration */
ngx_http_access_create_loc_conf, /* create location configuration */ ngx_http_access_merge_loc_conf /* merge location configuration */ };
|
static ngx_int_t ngx_http_access_init (ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h |