首先先看一个图:


上图是nginx listen端口配置形成一个结构图,有几点需要提醒一下:
1. 每个端口会有一个ngx_http_port_t结构,相同端口配置会ngx_http_conf_addr_t结构存放到addrs变量中。
2. 并不是所有相同端口都要创建ngx_http_conf_addr_t,当出现listen addr:port配置时,如果addr跟之前
同一port配置也相同时,则不创建该结构,只是将该port所在的server conf,即cscf放到ngx_http_conf_addr_t结构的servers
数组中。若addr部分不同,也会创建一个新的ngx_http_conf_addr_t。
在所有解析完配置后,ngx_http_optimize_servers会做一些整理优化工作,最重要的是为后面开启端口监听做准备。
对于每个port,会调用ngx_http_init_listening:
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; // 同一个端口的配置,即 ngx_http_conf_addr_t会有作排序操作,下面的注释做了介绍 // listen的配置还有不少细节,可以参考官方wiki: http:nginx.org/HttpCoreModule#listen /* * 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. */ // 查看该端口的配置里面有个出现通配情况的 if (addr[last - 1].opt.wildcard) { // 只把最后一个通配的设置为bind,这样前面的通配不具有bind标记 // 对于bind的标记,典型的情况(已排序好的)是 :binded,nobind,wildcard(nobind),wildcard(binded) addr[last - 1].opt.bind = 1; bind_wildcard = 1; } else { bind_wildcard = 0; } i = 0; while (i < last) { // 除了有通配的配置之外,还存在明确指出需要bind的配置,需要处理该配置 // 什么情况下是明确指出要bind的,可参考函数ngx_http_core_listen if (bind_wildcard && !addr[i].opt.bind) { i++; continue; } // 需要监听的port,分配一个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; } ls->servers = hport; if (i == last - 1) { // 到这里,就到了处理该结束的时候了,当last > 1意味着配置中存在nobind和 // wildcard的类型,即此时该端口被看做对应多个地址,对于这中情况的处理可以参考ngx_http_init_request hport->naddrs = last; } else { // 当前port为明确需要bind的,naddrs为1的含义是该port被明确bind到某个地址 hport->naddrs = 1; // 下面addr++,从下一面port继续处理,即相对与addr来说,数组下边自然是从0开始啦 i = 0; } switch (ls->sockaddr->sa_family) { default: /* AF_INET */ if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) { return NGX_ERROR; } break; } // 一旦某个标记为bind的端口处理完,addr就从下一个开始 addr++; // 从下一个addr开始到结束的port个数 last--; } return NGX_OK; }
然后我们看ngx_http_init_request函数:if (port->naddrs > 1) { // naddrs > 1的情况前面已经分析过了,最终要使用哪个地址,就通过ngx_connection_local_sockaddr // 来处理了。 /* * 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 */ // 通过这个函数,实质上就是调用了getsockname,获得该连接的服务器端ip if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { ngx_http_close_connection(c); return; } switch (c->local_sockaddr->sa_family) { default: /* AF_INET */ sin = (struct sockaddr_in *) c->local_sockaddr; addr = port->addrs; /* the last address is "*" */ // 如果与该port上其他非binded的某个配置,具有相同的ip,就是用它的配置,否则就用最后这个的配置 for (i = 0; i < port->naddrs - 1; i++) { if (addr[i].addr == sin->sin_addr.s_addr) { break; } } addr_conf = &addr[i].conf; break; } } else { // naddrs == 1 switch (c->local_sockaddr->sa_family) { default: /* AF_INET */ addr = port->addrs; addr_conf = &addr[0].conf; break; } } // 上面的操作最主要的作用就是拿到cscf,即核心的server conf,ngx_http_core_srv_conf_t结构。 // 关于default_server,在ngx_http_add_address处理,第一次添加的port会设置default_server // 为该port的当前cscf,同时在ngx_http_add_addresses中会针对相同addr:port的,修改一下 // default_server的指向,具体可以参考该函数。 cscf = addr_conf->default_server; // 得到了cscf,剩下的就好说了。这样发往该端口的请求,它的相关配置就得到了, r->main_conf = cscf->ctx->main_conf; r->srv_conf = cscf->ctx->srv_conf; r->loc_conf = cscf->ctx->loc_conf;
在处理请求时,相关模块的配置通常通过一些宏来获得:
#define ngx_http_get_module_main_conf(r, module) (r)->main_conf[module.ctx_index]
#define ngx_http_get_module_srv_conf(r, module) (r)->srv_conf[module.ctx_index]
#define ngx_http_get_module_loc_conf(r, module) (r)->loc_conf[module.ctx_index]
#define ngx_http_get_module_main_conf(r, module) (r)->main_conf[module.ctx_index]
#define ngx_http_get_module_srv_conf(r, module) (r)->srv_conf[module.ctx_index]
#define ngx_http_get_module_loc_conf(r, module) (r)->loc_conf[module.ctx_index]
需要注意的是,这里获得配置都是http core的,特别是对于location的配置,不同的模块可能有自己的需求,所以才有了http处理阶段中NGX_HTTP_FIND_CONFIG_PHASE的出现,这个阶段的主要作用就是为了根据具体的url,在核心location配置中的static_locations及regex_locations来查找相应location的配置。具体地,可以参考ngx_http_core_find_location。在看这个之前,需要的了解的是nginx location三叉树的结构。普通的location,一般指非正则的,都是放在static_locations管理的三叉树里,正则的配置则放到regex_locations链表里。明确了这些概念之后,函数的逻辑就很简单了。
上一篇:
nginx http处理过程分析之细节
下一篇:
nginx keepalive连接回收机制