nginx&http 第二章 ngx 事件event初始化 ngx_event_process_init
|----------(ngx_worker_process_cycle->ngx_worker_process_init)
|--------->for(;;) {ngx_process_events_and_timers()}
ngx_start_worker_processes---| ngx_processes[]相关的操作赋值流程
|----------ngx_pass_open_channel
1、ngx_worker_process_init 工作进程初始化的时候,调用init_process的回调函数,其回调函数为ngx_event_process_init
ngx_event_process_init 实现:
/*
|----------(ngx_worker_process_cycle->ngx_worker_process_init)
ngx_start_worker_processes---| ngx_processes[]相关的操作赋值流程
|----------ngx_pass_open_channel
*/
static void
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{ //主要工作是把CPU和进程绑定 创建epoll_crate等
sigset_t set;
uint64_t cpu_affinity;
ngx_int_t n;
ngx_uint_t i;
struct rlimit rlmt;
ngx_core_conf_t *ccf;
ngx_listening_t *ls; if (ngx_set_environment(cycle, NULL) == NULL) {
/* fatal */
exit(2);
} ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); if (worker >= 0 && ccf->priority != 0) { /*设置优先级*/
if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setpriority(%d) failed", ccf->priority);
}
} if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile; //RLIMIT_NOFILE指定此进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
if (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setrlimit(RLIMIT_NOFILE, %i) failed",
ccf->rlimit_nofile);
}
} if (ccf->rlimit_core != NGX_CONF_UNSET) {
rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
rlmt.rlim_max = (rlim_t) ccf->rlimit_core;
//修改工作进程的core文件尺寸的最大值限制(RLIMIT_CORE),用于在不重启主进程的情况下增大该限制。
if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setrlimit(RLIMIT_CORE, %O) failed",
ccf->rlimit_core);
}
ngx_log_debugall(cycle->log, 0, "setrlimit(RLIMIT_CORE, &rlmt) OK,rlimit_core:%O",ccf->rlimit_core);
} if (geteuid() == 0) {
if (setgid(ccf->group) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"setgid(%d) failed", ccf->group);
/* fatal */
exit(2);
} if (initgroups(ccf->username, ccf->group) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"initgroups(%s, %d) failed",
ccf->username, ccf->group);
} if (setuid(ccf->user) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"setuid(%d) failed", ccf->user);
/* fatal */
exit(2);
}
} if (worker >= 0) {
cpu_affinity = ngx_get_cpu_affinity(worker); if (cpu_affinity) {
ngx_setaffinity(cpu_affinity, cycle->log);
}
} #if (NGX_HAVE_PR_SET_DUMPABLE) /* allow coredump after setuid() in Linux 2.4.x */ if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"prctl(PR_SET_DUMPABLE) failed");
} #endif if (ccf->working_directory.len) { //路径必须存在,否则返回错误
if (chdir((char *) ccf->working_directory.data) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"chdir(\"%s\") failed", ccf->working_directory.data);
/* fatal */
exit(2);
}
ngx_log_debugall(cycle->log, 0, "chdir %V OK", &ccf->working_directory);
} sigemptyset(&set); if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigprocmask() failed");
} srandom((ngx_pid << 16) ^ ngx_time()); /*
* disable deleting previous events for the listening sockets because
* in the worker processes there are no events at all at this point
*/
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
ls[i].previous = NULL;
} for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->init_process) {
if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) { //ngx_event_process_init等
/* fatal */
exit(2);
}
}
} /* 用socketpair生成两个sock[0]和sock[1]用于父进程和子进程的通信,当父进程使用其中一个socket时,为什么要调用close,关闭子进程的sock[1],代码如下:
int r = socketpair( AF_UNIX, SOCK_STREAM, 0, fd );
if ( fork() ) {
Parent process: echo client
int val = 0;
close( fd[1] );
while ( 1 ) {
sleep( 1 );
++val;
printf( "Sending data: %d\n", val );
write( fd[0], &val, sizeof(val) );
read( fd[0], &val, sizeof(val) );
printf( "Data received: %d\n", val );
}
}
else {
Child process: echo server
int val;
close( fd[0] );
while ( 1 ) {
read( fd[1], &val, sizeof(val) );
++val;
write( fd[1], &val, sizeof(val) );
}
}
}
转载出处:http://blog.csdn.net/sunnyboychina/archive/2007/11/14/1884076.aspx
调用socketpair创建的两个socket都是打开的,fork后子进程会继承这两个打开的socket。为了实现父子进程通过socket pair(类似于管道)通信,必须保证父子进程分别open某一个socket。 channel[0] 是用来发送信息的,channel[1]是用来接收信息的。那么对自己而言,它需要向其他进程发送信息,需要保留其它进程的channel[0],
关闭channel[1]; 对自己而言,则需要关闭channel[0]。 最后把ngx_channel放到epoll中,从第一部分中的介绍我们可以知道,这个ngx_channel
实际就是自己的 channel[1]。这样有信息进来的时候就可以通知到了。
*/
//关闭所有其它子进程对应的 channel[1] 和 自己的 channel[0]。从而实现子进程的channel[1]和主进程的channel[0]通信
for (n = 0; n < ngx_last_process; n++) { if (ngx_processes[n].pid == -1) {
continue;
} if (n == ngx_process_slot) {
continue;
} if (ngx_processes[n].channel[1] == -1) {
continue;
} if (close(ngx_processes[n].channel[1]) == -1) { //关闭除本进程以外的其他所有进程的读端
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
}
} if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) { //关闭本进程的写端 ,剩下的一条通道还是全双工的
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
} #if 0
ngx_last_process = 0;
#endif //调用epoll add 把ngx_chanel 加入epoll 中
if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
ngx_channel_handler) //在ngx_spawn_process中赋值
== NGX_ERROR)
{
/* fatal */
exit(2);
}
}
module->actions.init 主要是调用epoll/kqueque等模型模块的init初始化方法。epoll调用是ngx_epoll_init这个方法。
ecf->use在配置文件中,我们配置了:“use epoll;”。但是这里存储的是 epoll/kqueue等模块是索引ID。通过索引ID就可以快速在cycle->modules找到对应的模块。
CP连接和读取事件逻辑:
在 Nginx 的初始化启动过程中,worker 工作进程会调用事件模块的ngx_event_process_init 方法为每个监听套接字ngx_listening_t 分配一个 ngx_connection_t 连接,
并设置该连接上读事件的回调方法handler 为ngx_event_accept,同时将读事件挂载到epoll 事件机制中等待监听套接字连接上的可读事件发生,
到此,Nginx 就可以接收并处理来自客户端的请求。当监听套接字连接上的可读事件发生时,即该连接上有来自客户端发出的连接请求,
则会启动读read事件的handler 回调方法ngx_event_accept,在ngx_event_accept 方法中调用accept() 函数接收来自客户端的连接请求,
成功建立连接之后,ngx_event_accept 函数调用监听套接字ngx_listen_t上的handler 回调方法ls->handler(c)(该回调方法就是ngx_http_init_connection 主要用于初始化ngx_connection_t客户端连接)。
ngx_http_init_connection 会将rev->handler的回调函数修改成: ngx_http_wait_request_handler,
该回调函数主要用于处理read事件的数据读取。后续当有read事件上来的时候,就会回调ngx_http_wait_request_handler函数,而非ngx_event_accept函数
3、ngx_event_accept中会调用ls->handler回调函数ngx_http_init_connection
4、ngx_http_init_connection初始化一个http的连接,并且将rev->handler回调函数修改成ngx_http_wait_request_handler,
主要用于接收read事件数据。所有后面进入事件循环后,read事件调用的是ngx_http_wait_request_handler函数。
//设置ngx_listening_t的handler,这个handler会在监听到客户端连接时被调用,具体就是在ngx_event_accept函数中,ngx_http_init_connection函数顾名思义,就是初始化这个新建的连接
void
ngx_http_init_connection(ngx_connection_t *c)
//当建立连接后开辟ngx_http_connection_t结构,这里面存储该服务器端ip:port所在server{}上下文配置信息,和server_name信息等,然后让
//ngx_connection_t->data指向该结构,这样就可以通过ngx_connection_t->data获取到服务器端的serv loc 等配置信息以及该server{}中的server_name信息 {
ngx_uint_t i;
ngx_event_t *rev;
struct sockaddr_in *sin;
ngx_http_port_t *port;
ngx_http_in_addr_t *addr;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
ngx_http_in6_addr_t *addr6;
#endif //注意ngx_connection_t和ngx_http_connection_t的区别,前者是建立连接accept前使用的结构,后者是连接成功后使用的结构
hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
if (hc == NULL) {
ngx_http_close_connection(c);
return;
} //在服务器端accept客户端连接成功(ngx_event_accept)后,会通过ngx_get_connection从连接池获取一个ngx_connection_t结构,也就是每个客户端连接对于一个ngx_connection_t结构,
//并且为其分配一个ngx_http_connection_t结构,ngx_connection_t->data = ngx_http_connection_t,见ngx_http_init_connection
c->data = hc; /* find the server configuration for the address:port */ port = c->listening->servers; if (port->naddrs > 1) { /*
* there are several addresses on this port and one of them
* is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
* is required to determine a server address
*/
//说明listen ip:port存在几条没有bind选项,并且存在通配符配置,如listen *:port,那么就需要通过ngx_connection_local_sockaddr来确定
//究竟客户端是和那个本地ip地址建立的连接
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { //
ngx_http_close_connection(c);
return;
} switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) c->local_sockaddr; addr6 = port->addrs; /* the last address is "*" */ for (i = 0; i < port->naddrs - 1; i++) {
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
break;
}
} hc->addr_conf = &addr6[i].conf; break;
#endif default: /* AF_INET */
sin = (struct sockaddr_in *) c->local_sockaddr; addr = port->addrs; /* the last address is "*" */
//根据上面的ngx_connection_local_sockaddr函数获取到客户端连接到本地,本地IP地址获取到后,遍历ngx_http_port_t找到对应
//的IP地址和端口,然后赋值给ngx_http_connection_t->addr_conf,这里面存储有server_name配置信息以及该ip:port对应的上下文信息
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
} /*
这里也体现了在ngx_http_init_connection中获取http{}上下文ctx,如果客户端请求中带有host参数,则会继续在ngx_http_set_virtual_server
中重新获取对应的server{}和location{},如果客户端请求不带host头部行,则使用默认的server{},见 ngx_http_init_connection
*/
hc->addr_conf = &addr[i].conf; break;
} } else { switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6)
case AF_INET6:
addr6 = port->addrs;
hc->addr_conf = &addr6[0].conf;
break;
#endif default: /* AF_INET */
addr = port->addrs;
hc->addr_conf = &addr[0].conf;
break;
}
} /* the default server configuration for the address:port */
//listen add:port对于的 server{}配置块的上下文ctx
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 (NGX_HTTP_V2)
/* 这里放在SSL的前面是,如果没有配置SSL,则直接不用进行SSL协商而进行HTTP2处理ngx_http_v2_init */
if (hc->addr_conf->http2) {
rev->handler = ngx_http_v2_init;
}
#endif #if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf; sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); if (sscf->enable || hc->addr_conf->ssl) { c->log->action = "SSL handshaking"; if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"no \"ssl_certificate\" is defined "
"in server listening on SSL port");
ngx_http_close_connection(c);
return;
} hc->ssl = 1; rev->handler = ngx_http_ssl_handshake;
}
}
#endif if (hc->addr_conf->proxy_protocol) {
hc->proxy_protocol = 1;
c->log->action = "reading PROXY protocol";
} /*
如果新连接的读事件ngx_event_t结构体中的标志位ready为1,实际上表示这个连接对应的套接字缓存上已经有用户发来的数据,
这时就可调用上面说过的ngx_http_init_request方法处理请求。
*/
//这里只可能是当listen的时候添加了defered参数并且内核支持,在ngx_event_accept的时候才会置1,才可能执行下面的if里面的内容,否则不会只需if里面的内容
if (rev->ready) {
/* the deferred accept(), iocp */
if (ngx_use_accept_mutex) { //如果是配置了accept_mutex,则把该rev->handler延后处理,
//实际上执行的地方为ngx_process_events_and_timers中的ngx_event_process_posted
ngx_post_event(rev, &ngx_posted_events);
return;
} rev->handler(rev); //ngx_http_wait_request_handler
return;
} /*
在有些情况下,当TCP连接建立成功时同时也出现了可读事件(例如,在套接字listen配置时设置了deferred选项时,内核仅在套接字上确实收到请求时才会通知epoll
调度事件的回调方法。当然,在大部分情况下,ngx_http_init_request方法和
ngx_http_init_connection方法都是由两个事件(TCP连接建立成功事件和连接上的可读事件)触发调用的
*/ /*
调用ngx_add_timer方法把读事件添加到定时器中,设置的超时时间则是nginx.conf中client_header_timeout配置项指定的参数。
也就是说,如果经过client_header_timeout时间后这个连接上还没有用户数据到达,则会由定时器触发调用读事件的ngx_http_init_request处理方法。
*/
ngx_add_timer(rev, c->listening->post_accept_timeout, NGX_FUNC_LINE); //把接收事件添加到定时器中,当post_accept_timeout秒还没有客户端数据到来,就关闭连接
ngx_reusable_connection(c, 1); if (ngx_handle_read_event(rev, 0, NGX_FUNC_LINE) != NGX_OK) { //当下次有数据从客户端发送过来的时候,会在ngx_epoll_process_events把对应的ready置1。
ngx_http_close_connection(c);
return;
}
}
5、ls->handler的回调函数是如何赋值的 什么时候设置为 ngx_http_init_connection
看一下ngx_http.c中的ngx_http_block函数,该函数在模块命令初始化的时候会回调,并且调用
ngx_http_optimize_servers方法,并且进一步调用ngx_http_init_listening初始化的监听器,然后最终调用ngx_http_add_listening方法
解析HTTP配置的流程
HTTP框架解析配置项ngx_http_module和ngx_
http_core_module模块,所谓的HTTP框架主要由这两个模块组成),下面解释每个流程
的意义。
1)主循环是指Nginx进程的主循环,主循环只有调用配置文件解析器才能
解析nginx.conf文件(这里的“主循环”是指解析全部配置文件的循环代码,图8-6的第4
步,为了便于理解,可以认为是Nginx框架代码在循环解析配置项)。
2)当发现配置文件中含有http{)关键字时,HTTP框架开始启动,这一过程详见10.7
节描述的ngx_http_block方法。
3) HTTP框架会初始化所有HTTP模块的序列号,并创建3个数组用于存储所有HTTP
模块的create—main- conf、create—srv—conf、create—loc—conf方法返回的指针地址,并把这3
个教组的地址保存到ngx_http_conf_ ctx-t结构中。
4)调用每个HTTP模块(当然也包括例子中的mytest模块)的create main conf.
create—srv_conf. create一loc—conf(如果实现的话)方法。
5)把各HTTP模块上述3个方法返回的地址依次保存到ngx_http_conf ctx_t结构体的
3个数组中。
6)调用每个HTTP模块的preconfiguration方法(如果实现的话)。
7)注意,如果preconfiguration返回失败,那么Nginx进程将会停止。
8) HTTP框架开始循环解析nginx.conf文件中http{...}里面的所有配置项,
过程到第19步才会返回。
9)配置文件解析器在检测到1个配置项后,会遍历所有的HTTP模块,
ngx_command_t数组中的name项是否与配置项名相同。
注意,这个
检查它们的
10)如果找到有1个HTTP模块(如mytest模块)对这个配置项感兴趣(如test- myconfig
配置项),就调用ngx_command_t结构中的set方法来处理。
11) set方法返回是否处理成功。如果处理失败,那么Nginx进程会停止。
12)配置文件解析器继续检测配置项。如果发现server{...)配置项,就会调用ngx_http_
core__ module模块来处理。因为ngx_http_core__ module模块明确表示希望处理server{}块下
的配置项。注意,这次调用到第18步才会返回。
13) ngx_http_core_module棋块在解析server{...}之前,也会如第3步一样建立ngx_
http_conf_ctx_t结构,并建立数组保存所有HTTP模块返回的指针地址。然后,它会调用每
个HTTP模块的create—srv_ conf、create- loc—conf方法(如果实现的话)。
14)将上一步各HTTP模块返回的指针地址保存到ngx_http_conf_ ctx-t对应的数组中。
15)开始调用配置文件解析器来处理server{...}里面的配置项,注意,这个过程在第17
步返回。
16)继续重复第9步的过程,遍历nginx.conf中当前server{...)内的所有配置项。
17)配置文件解析器继续解析配置项,发现当前server块已经遍历到尾部,说明server
块内的配置项处理完毕,返回ngx_http_core__ module模块。
18) http core模块也处理完server配置项了,返回至配置文件解析器继续解析后面的配
置项。
19)配置文件解析器继续解析配置项,这时发现处理到了http{...)的尾部,返回给
HTTP框架继续处理。
20)在第3步和第13步,以及我们没有列幽来的某些步骤中(如发现其他server块
或者location块),都创建了ngx_http_conf_ ctx_t结构,这时将开始调用merge_srv_conf、
merge_loc_conf等方法合并这些不同块(http、server、location)中每个HTTP模块分配的数
据结构。
21) HTTP框架处理完毕http配置项(也就是ngx_command_t结构中的set回调方法处
理完毕),返回给配置文件解析器继续处理其他http{...}外的配置项。
22)配置文件解析器处理完所有配置项后会告诉Nginx主循环配置项解析完毕,这时
Nginx才会启动Web服务器。 注意 并没有列出解析location{...)块的流程,实际上,解析location与解析
server并没有本质上的区别。
//从ngx_http_module模块里面的http命令解析走到这里
/*
cf空间始终在一个地方,就是ngx_init_cycle中的conf,使用中只是简单的修改conf中的ctx指向已经cmd_type类型,然后在解析当前{}后,重新恢复解析当前{}前的配置
参考"http" "server" "location"ngx_http_block ngx_http_core_server ngx_http_core_location ngx_http_core_location
*/
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
//这里的cf是从ngx_conf_handler里面的if (cmd->type & NGX_DIRECT_CONF)判断里面确定了该cf为
{//图形化参考:深入理解NGINX中的图9-2 图10-1 图4-2,结合图看,并可以配合http://tech.uc.cn/?p=300看
char *rv;
ngx_uint_t mi, m, s;
ngx_conf_t pcf;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t **cscfp;
ngx_http_core_main_conf_t *cmcf; /* the main http context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
} //conf为ngx_conf_handler中的conf = confp[ngx_modules[i]->ctx_index];也就是conf指向的是ngx_cycle_s->conf_ctx[],
//所以对conf赋值就是对ngx_cycle_s中的conf_ctx赋值
*(ngx_http_conf_ctx_t **) conf = ctx; //图形化参考:深入理解NGINX中的图9-2 图10-1 图4-2,结合图看,并可以配合http://tech.uc.cn/?p=300看 /* count the number of the http modules and set up their indices */ ngx_http_max_module = 0;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
} ngx_modules[m]->ctx_index = ngx_http_max_module++; //二级类型按照在ngx_modules中的顺序排序
} /* the http main_conf context, it is the same in the all http contexts */ ctx->main_conf = ngx_pcalloc(cf->pool,
sizeof(void *) * ngx_http_max_module);
if (ctx->main_conf == NULL) {
return NGX_CONF_ERROR;
} /*
* the http null srv_conf context, it is used to merge
* the server{}s' srv_conf's
*/ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
} /*
* the http null loc_conf context, it is used to merge
* the server{}s' loc_conf's
*/ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
} /*
* create the main_conf's, the null srv_conf's, and the null loc_conf's
* of the all http modules
*/
//执行所有ngx_modules[m]->type = NGX_HTTP_MODULE的http模块的crate函数来创建对应模块的conf参数,用于后面保存从配置文件中解析出的参数信息
//http{}下为所有的NGX_HTTP_MODULES模块开辟了main srv loc空间
//按照模块类型进行合并 http{} server{} location{}都属于同一个ngx_http_core_module模块,他们的init_main_conf都是一样的
/*
http {
xxxx
server {
location /xxx {
}
}
}
这种情况的配置文件,在执行到http的时候开辟ngx_http_conf_ctx_t会分别调用一次main crv loc_creat,执行到server时开辟ngx_http_conf_ctx_t会调用srv_creat loc_creat, 执行到location时开辟ngx_http_conf_ctx_t会调用一次loc_creat
所以这种情况会调用1次main_creat 2才srv_creat 3次loc_creat。 http {
xxxx
server {
location /xxx {
}
} server {
location /yyy {
}
}
}
这种情况的配置文件,在执行到http的时候开辟ngx_http_conf_ctx_t会分别调用一次main crv loc_creat,执行到server时开辟ngx_http_conf_ctx_t会调用srv_creat loc_creat, 执行到location时开辟ngx_http_conf_ctx_t会调用一次loc_creat
所以这种情况会调用1次main_creat 1+2才srv_creat 1+2+2次loc_creat。 需要ngx_http_block ngx_http_core_server ngx_http_core_location配合看代码可以看出来
*/
for (m = 0; ngx_modules[m]; m++) { //注意这里为所有的NGX_HTTP_MODULE开辟了main_conf srv_conf loc_conf空间,也就是在http{}的时候为所有main srv loc开辟了空间
if (ngx_modules[m]->type != NGX_HTTP_MODULE) { //http{}相关配置结构创建首先需要执行ngx_http_core_module,而后才能执行对应的http子模块
continue;
} module = ngx_modules[m]->ctx;
mi = ngx_modules[m]->ctx_index; //mi实际上是依次递增的,见签名的ctx_index赋值处 if (module->create_main_conf) {
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
} if (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
} if (module->create_loc_conf) {
ctx->loc_conf[mi] = module->create_loc_conf(cf);
if (ctx->loc_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
} pcf = *cf; //零时保存在解析到http{}时候,在这之前的cf
cf->ctx = ctx;//零时指向这块新分配的ctx,为存储ngx_http_core_commands开辟的空间 //执行各个模块的preconfiguration
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
} module = ngx_modules[m]->ctx; if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
} /* parse inside the http{} block */ cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
if (rv != NGX_CONF_OK) {
goto failed;
} /*
* init http{} main_conf's, merge the server{}s' srv_conf's
* and its location{}s' loc_conf's
*/ cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; //见ngx_http_core_create_main_conf
cscfp = cmcf->servers.elts;//一级main_conf中的server中保存的所有二级server结构信息 for (m = 0; ngx_modules[m]; m++) { //按照模块类型进行合并 http{} server{} location{}都属于同一个ngx_http_core_module模块,他们的init_main_conf都是一样的
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
} module = ngx_modules[m]->ctx;
mi = ngx_modules[m]->ctx_index; /* init http{} main_conf's */ if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]); //见ngx_http_core_init_main_conf
if (rv != NGX_CONF_OK) {
goto failed;
}
} //cf->ctx为http{}的上下文ctx,cmcf为server{}中的所有上下文ctx
rv = ngx_http_merge_servers(cf, cmcf, module, mi);//合并server{}及其以下的local{}
if (rv != NGX_CONF_OK) {
goto failed;
}
} /* create location trees */
/*
经过配置的读取之后,所有server都被保存在http core模块的main配置中的servers数组中,而每个server里面的location都被按配置中
出现的顺序保存在http core模块的loc配置的locations队列中,上面的代码中先对每个server的location进行排序和分类处理,这一步
发生在 ngx_http_init_location()函数中:
*/
for (s = 0; s < cmcf->servers.nelts; s++) {
/*
clcf是server块下的ngx_http_core_loc_conf_t结构体,locations成员以双向链表关联着隶属于这个server块的所有location块对应的ngx_http_core_loc_conf_t结构体
*/
//cscfp[]->ctx就是解析到二级server{}时所在的上下文ctx
clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];//每个server中的loc空间,其实他也是该server下location{}中的loc空间的头部,参考ngx_http_add_location /*
将ngx_http_core_loc_conf_t组成的双向链表按照location匹配字符串进行排序。注意:这个操作是递归进行的,如果某个location块下还具有其他location,那么它的locations链表也会被排序
*/
if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
//srver{}下所有loc空间(包括server自己的以及其下的location),这里的clcf是解析到server{}行的时候创建的loc_conf
return NGX_CONF_ERROR;
} /*
根据已经按照location字符串排序过的双向链表,快速地构建静态的二叉查找树。与ngx_http_init_locations方法类似,速个操作也是递归进行的
*/
/*
下面的ngx_http_init_static_location_trees函数就会将那些普通的location(就是ngx_http_init_locations中name noname regex以外的location(exact/inclusive)),
即staticlocation,进行树化(一种三叉树)处理,之所以要做这样的处理,是为了在处理http请求时能高效的搜索的匹配的location配置。
*/
/*
根据已经按照location字符串排序过的双向链表,快速地构建静态的三叉查找树。与ngx_http_init_locations方法类似,速个操作也是递归进行的
*/ //clcf中现在只有普通staticlocation
if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
} if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
} if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
} for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
} module = ngx_modules[m]->ctx; if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
} if (ngx_http_variables_init_vars(cf) != NGX_OK) {
return NGX_CONF_ERROR;
} /*
* http{}'s cf->ctx was needed while the configuration merging
* and in postconfiguration process
*/ *cf = pcf;//恢复到上层的ngx_conf_s地址 if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
} /* optimize the lists of ports, addresses and server names */
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
return NGX_CONF_ERROR;
} return NGX_CONF_OK; failed: *cf = pcf; return rv;
} /*
这个函数就是遍历所有的端口号,将端口号对应的地址结构的hash、wc_head和wc_tail初始化,这个在初始化后面的ngx_listening_t的servers字
段时会用到。然后调用ngx_http_init_listening函数完成ngx_listening_t初始化。
*/
static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
ngx_array_t *ports)
{
ngx_uint_t p, a;
ngx_http_conf_port_t *port;
ngx_http_conf_addr_t *addr; if (ports == NULL) {
return NGX_OK;
} port = ports->elts;
for (p = 0; p < ports->nelts; p++) {
//将addrs排序,带通配符的地址排在后面, (listen 1.2.2.2:30 bind) > listen 1.1.1.1:30 > listen *:30
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs); /*
* check whether all name-based servers have the same
* configuration as a default server for given address:port
*/ addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
/* 多个server{}下面有listen IP:port ,并且每个server{}中的端口都相等,则他们保存在同一个port[i]中,只是ip地址不一样,以addrs区分 */
if (addr[a].servers.nelts > 1
#if (NGX_PCRE)
|| addr[a].default_server->captures
#endif
)
{ //相同端口,不同IP地址对应的server{},把每个server中的server_names配置进行hash存储
/*
初始addr(ngx_http_conf_addr_t)中的hash、wc_head和wc_tail哈希表。 这些哈希表以server_name(虚拟主机名)为key,server块
的ngx_http_core_srv_conf_t为 value,用于在处理请求时,根据请求的host请求行快速找到处理该请求的server配置结构。
*/
if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
return NGX_ERROR;
}
}
} if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
return NGX_ERROR;
}
} return NGX_OK;
} static ngx_int_t
ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
{
ngx_uint_t i, last, bind_wildcard;
ngx_listening_t *ls;
ngx_http_port_t *hport;
ngx_http_conf_addr_t *addr; addr = port->addrs.elts;
last = port->addrs.nelts; /*
* If there is a binding to an "*:port" then we need to bind() to
* the "*:port" only and ignore other implicit bindings. The bindings
* have been already sorted: explicit bindings are on the start, then
* implicit bindings go, and wildcard binding is in the end. //例如有listen 80(implicit bindings); listen *:80,则第一个无效,直接用第二个就行了
*/ if (addr[last - 1].opt.wildcard) { //"*:port" addr是拍了序的,见ngx_http_optimize_servers,最后面的是通配符
addr[last - 1].opt.bind = 1; //如果是通配符,这里把bind值1
bind_wildcard = 1; //表示有通配符listen } else {
bind_wildcard = 0;
} i = 0; /*
这个函数就是遍历某个端口port对应的所有address,如果所有address中不包含通配符,则对所有的address:port调用ngx_http_add_listening分配一
个listen结构和ngx_http_port_t结构,并初始化它们。如果存在address包含通配符,则如果address:port需要bind,分配一个listen结构和
ngx_http_port_t结构,并初始化它们,对所有address:port不需要bind的,它们和包含通配符*:port共同使用一个listen结构和ngx_http_port_t结构,
并且listen结构中包含的地址是*:port,所以最好bind的地址是*:port。所有的listen都会存放在全局变量ngx_cycle的listening数组中,这样后面就
可以利用这些address:port信息建立每个套接字了。
*/
while (i < last) {
//last代表的是address:port的个数, 如果没有通配符配置项,则有多少个last,就有多少次循环。bind=1的有多少次就执行多少次,如果有通配符和bind = 0的listen配置,
//则在后面的if (bind_wildcard && !addr[i].opt.bind)进行continue,也就是这些未精确配置项合在一起在后面置执行一次分配ngx_http_port_t空间,把他们算在
//addr[i]中,这里的i是通配符所在位置。 //对所有address:port不需要bind的,它们和包含通配符*:port共同使用一个listen结构和ngx_http_port_t结构, 并且listen结构中包含的地址是*:port,所以最好bind的地址是*:port
if (bind_wildcard && !addr[i].opt.bind) { //如果是通配符*:port,或者是listen配置没有加bind参数
i++;//如果有通配符配置,并且bind = 0则把这些bind=0和通配符配置算作一项,执行后面的操作。通配符的bind在该函数前面置1,见addr[last - 1].opt.bind = 1
continue;
} //为该listen创建对应的ngx_listening_t结构并赋值
ls = ngx_http_add_listening(cf, &addr[i]);
if (ls == NULL) {
return NGX_ERROR;
} hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
if (hport == NULL) {
return NGX_ERROR;
} /*
* servers会用来保存虚拟主机的信息,在处理请求时会赋值给request 用于进行虚拟主机的匹配
*/
ls->servers = hport; //如果是未精确配置的listen(bind = 0并且有配置一项通配符,则这里的i是通配符所在addr[]的位置),如果没有配置通配符,则有多少个listen配置就会执行这里多少次。
//只是在出现通配符listen的配置中,把未精确配置的所有项合到通配符所在addr[]位置
hport->naddrs = i + 1; //保护listen通配符配置,并且没有bind的listen项数
switch (ls->sockaddr->sa_family) { #if (NGX_HAVE_INET6)
case AF_INET6:
if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
return NGX_ERROR;
}
break;
#endif
default: /* AF_INET */
if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) { //后面有addr++,所以这里的addr对应的是addr[i]的地址
return NGX_ERROR;
}
break;
} if (ngx_clone_listening(cf, ls) != NGX_OK) {
return NGX_ERROR;
} addr++;
last--;
} return NGX_OK;
} //ngx_event_process_init
//master进程执行ngx_clone_listening中如果配置了多worker,监听80端口会有worker个listen赋值,master进程在ngx_open_listening_sockets
//中会监听80端口worker次,那么子进程创建起来后,不是每个字进程都关注这worker多个 listen事件了吗?为了避免这个问题,nginx通过
//在子进程运行ngx_event_process_init函数的时候,通过ngx_add_event来控制子进程关注的listen,最终实现只关注master进程中创建的一个listen事件 //ngx_listening_t创建空间,并通过addr赋值初始化
static ngx_listening_t *
ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
ngx_listening_t *ls;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf; //为listen配置创建对应的ngx_listening_t结构,并赋值IP地址等,里面也会完成IP地址字符串格式的转换
ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
if (ls == NULL) {
return NULL;
} ls->addr_ntop = 1; // 设置ngx_listening_t的handler,这个handler会在监听到客户端连接时被调用,具体就是在ngx_event_accept函数中,ngx_http_init_connection函数顾名思义,就是初始化这个新建的连接
ls->handler = ngx_http_init_connection; cscf = addr->default_server;
ls->pool_size = cscf->connection_pool_size;
ls->post_accept_timeout = cscf->client_header_timeout; clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index]; ls->logp = clcf->error_log;
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error; //该listen上面的打印会加上listen后面的IP地址字符串格式 #if (NGX_WIN32)
{
ngx_iocp_conf_t *iocpcf = NULL; if (ngx_get_conf(cf->cycle->conf_ctx, ngx_events_module)) {
iocpcf = ngx_event_get_conf(cf->cycle->conf_ctx, ngx_iocp_module);
}
if (iocpcf && iocpcf->acceptex_read) {
ls->post_accept_buffer_size = cscf->client_header_buffer_size;
}
}
#endif ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf; ls->keepalive = addr->opt.so_keepalive;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
ls->keepidle = addr->opt.tcp_keepidle;
ls->keepintvl = addr->opt.tcp_keepintvl;
ls->keepcnt = addr->opt.tcp_keepcnt;
#endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
ls->accept_filter = addr->opt.accept_filter;
#endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
ls->deferred_accept = addr->opt.deferred_accept;
#endif #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
ls->ipv6only = addr->opt.ipv6only;
#endif #if (NGX_HAVE_SETFIB)
ls->setfib = addr->opt.setfib;
#endif #if (NGX_HAVE_TCP_FASTOPEN)
ls->fastopen = addr->opt.fastopen;
#endif #if (NGX_HAVE_REUSEPORT)
ls->reuseport = addr->opt.reuseport;
#endif return ls;
}
nginx&http 第二章 ngx 事件event初始化 ngx_event_process_init的更多相关文章
- nginx&http 第二章 ngx 事件event处理 数据结构
ngx_event.c :这个文件主要放置Nginx事件event模块的核心代码. 包含:进程事件分发器(ngx_process_events_and_timers).事件模块的模块和配置.模块初始化 ...
- nginx&http 第二章 ngx 事件event配置等初始化
event事件模块,配置分为两层:ngx_events_module 事件模块 和 ngx_event_core_module 事件核心模块.ngx_events_module:模块类型NGX_COR ...
- nginx&http 第三章 ngx 事件http 初始化1
在 http 配置块中,我们配置了 http 连接相关的信息,HTTP 框架也正是从这里启动的 在 nginx 初始化的过程中,执行了 ngx_init_cycle 函数,其中进行了配置文件解析,调用 ...
- nginx&http 第三章 ngx 事件event epoll 处理
1. epoll模块命令集 ngx_epoll_commands epoll模块上下文 ngx_epoll_module_ctx epoll模块配置 ngx_epoll_module static ...
- nginx&http 第三章 ngx 事件event accept epoll /init
tcp 三次握手成功后,listen fd 可读,在process_event_timer 中调用rev->handler(rev)处理: 其回调函数为: ngx_event_accept / ...
- nginx&http 第二章 ngx启动多进程
Nginx服务器使用 master/worker 多进程模式. 主进程(Master process)启动后,会接收和处理外部信号: 主进程启动后通过fork() 函数产生一个或多个子进程(work ...
- nginx&http 第三章 ngx http ngx_http_process_request_line读取和处理HTTP头部的行
在 ngx_http_wait_request_handler 的最后调用了 ngx_http_process_request_line 函数用来处理和解析这次请求的全文 在读事件被触发时,内核套接字 ...
- nginx&http 第三章 ngx http 框架处理流程
1. nginx 连接结构 ngx_connection_t 这个连接表示是客户端主动发起的.Nginx服务器被动接受的TCP连接,我们可以简单称其为被动连接.同时,在有些请求的处理过程中,Nginx ...
- Prism 文档 第二章 初始化Prism应用程序
第二章 初始化Prism应用程序 本章将讨论为了使一个Pr ...
随机推荐
- C#数据结构-栈
栈的定义不需要多说,相信大家都非常熟悉,但是,在实际应用中栈的应用我们很少想到会去用栈结构,先上代码看下用法: Stack st = new Stack(); st.Push('A'); st.Pus ...
- Rust之路(4)——所有权
[未经书面同意,严禁转载] -- 2020-10-14 -- 所有权是Rust的重中之重(这口气咋像高中数学老师 WTF......). 所有权是指的对内存实际存储的数据的访问权(包括读取和修改),在 ...
- JSX 详解
一 jsx 的本质是什么? jsx是语法糖,需要被编译成js才能运行. jsx 看似是html 结构,实质是js结构的语法糖,在代码编译阶段被编译成js结构.所以jsx的本质可描述为看似html结构的 ...
- day62 Pyhton 框架Django 05
内容回顾 1.变量 render(request,'模板文件名',{ k1:v1 }) {{ k1 }} {{ list.0 }} {{ dict.key }} {{ dict.keys }} {{ ...
- 【树形DP】CF 1293E Xenon's Attack on the Gangs
题目大意 vjudge链接 给n个结点,n-1条无向边.即一棵树. 我们需要给这n-1条边赋上0~ n-2不重复的值. mex(u,v)表示从结点u到结点v经过的边权值中没有出现的最小非负整数. 计算 ...
- 单调队列+线性dp题Watching Fireworks is Fun (CF372C)
一.Watching Fireworks is Fun(紫题) 题目:一个城镇有n个区域,从左到右1编号为n,每个区域之间距离1个单位距离节日中有m个烟火要放,给定放的地点ai,时间ti当时你在x,那 ...
- 如何将vscode代码快速同步到github/gitee上
用git实现源代码管理几乎是程序员的必备操作,下面是简单实现流程: 在vscode打开代码所在文件夹 在左侧栏点击源代码管理 初始化存储库 添加远程存储库 输入远程仓库地址(没有仓库的要先建个仓) 输 ...
- Linux文件系统和管理-2文件操作命令(中)
创建空文件和刷新时间 touch touch命令可以用来创建空文件或刷新文件的时间 touch 存在的文件刷新时间,不存在的文件创建空文件 生成指定日期的日志文件 [root@C8-1 ~]# dat ...
- Asp.Net实现局部刷新,ScriptManager和UpdatePanel控件的使用
<asp:CheckBoxList ID="LimitCollegeNo" runat="server" CellPadding="5" ...
- if else 太多?看我用 Java 8 轻松干掉!
之前我用 Java 8 写了一段逻辑,就是类似下面这样的例子: /* * 来源公众号:Java技术栈 */ if(xxxOrder != null){ if(xxxOrder.getXxxShippi ...