看到了后面才发现其实自己在server块命令这一部分并没有搞的太清楚,因而会影响到以后对代码的理解,因而从头又来看一遍,一定要把它搞明白。
首先我们来看ngx_http_core_module的main_conf结构:
typedef struct { /** * 存储所有的ngx_http_core_srv_conf_t,元素的个数等于server块的个数。 ,因为同一个http块命令可以配置多个server */ ngx_array_t servers; /* ngx_http_core_srv_conf_t */ /** * 包含所有phase,以及注册的phase handler,这些handler在处理http请求时, * 会被依次调用,通过ngx_http_phase_handler_t的next字段串联起来组成一个 * 链表。 */ ngx_http_phase_engine_t phase_engine; /** * 以hash存储的所有request header */ ngx_hash_t headers_in_hash; /** * 被索引的nginx变量 ,比如通过rewrite模块的set指令设置的变量,会在这个hash * 中分配空间,而诸如$http_XXX和$cookie_XXX等内建变量不会在此分配空间。 */ ngx_hash_t variables_hash; /** * ngx_http_variable_t类型的数组,所有被索引的nginx变量被存储在这个数组中。 * ngx_http_variable_t结构中有属性index,是该变量在这个数组的下标。 */ ngx_array_t variables; /* ngx_http_variable_t */ ngx_uint_t ncaptures; /** * server names的hash表的允许的最大bucket数量,默认值是512。 */ ngx_uint_t server_names_hash_max_size; ngx_uint_t server_names_hash_bucket_size; ngx_uint_t variables_hash_max_size; ngx_uint_t variables_hash_bucket_size; ngx_hash_keys_arrays_t *variables_keys; /** * 监听的所有端口,ngx_http_conf_port_t类型,其中包含socket地址信息。 */ ngx_array_t *ports; //保存ngx_http_conf_port_t结构的元素,在ngx_http_optimize_servers函数中,将会用到ports中的元素来创建监听 ngx_uint_t try_files; /* unsigned try_files:1 */ /** * 所有的phase的数组,其中每个元素是该phase上注册的handler的数组。 */ ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1]; } ngx_http_core_main_conf_t;
其有几个比较重要的域是:
(1)servers,因为整个http块命令中创建的配置结构的main_conf结构都是统一的,但是同一个http块命令中却可以配置多个server块命令,servers数组就是用来保存这些独立的server块配置的,其保存的没一个元素都是ngx_http_core_srv_conf_t类型的。
(2)ports,其也是一个数组,我们在listen指令中会指定监听的地址端口的信息,由于可能会同时监听多个,所以ports数组就用来保存所有的这些监听的信息,在listen指令的回调函数中会将这些配置信息保存到ports数组中,其中保存的元素是ngx_http_conf_port_t结构,而且最后会调用ngx_http_optimize_servers函数来根据其中保存的信息来生成最后的ngx_listening_t结构。
接下来我们来看ngx_http_core_module模块的srv_conf结构:
typedef struct { /* array of the ngx_http_server_name_t, "server_name" directive */ ngx_array_t server_names; //server的名字数组 ngx_http_server_name_t /* server ctx */ ngx_http_conf_ctx_t *ctx; // 指向包含它的ngx_http_conf_ctx_t结构,因为在server块命令的回调函数中会重新创建ngx_http_conf_ctx_t结构,但是其中的main_conf结构都是统一的 ngx_str_t server_name; //名字 size_t connection_pool_size; //connection池的大小 size_t request_pool_size; size_t client_header_buffer_size; ngx_bufs_t large_client_header_buffers; ngx_msec_t client_header_timeout; //请求超时时间 ngx_flag_t ignore_invalid_headers; ngx_flag_t merge_slashes; ngx_flag_t underscores_in_headers; unsigned listen:1; //区别当前server是否监听 #if (NGX_PCRE) unsigned captures:1; #endif ngx_http_core_loc_conf_t **named_locations; //该srv配置的location配置数组 } ngx_http_core_srv_conf_t;
其中有几个比较重要的域:
(1)ctx,我们已经知道在每一个server块命令中都会重新创建ngx_http_conf_ctx_t结构,它的main_conf中的数据都是统一的,ctx就是用来指向当前server块命令中自己创建的ngx_http_conf_ctx_t结构。
(2)listen:如果在当前server块命令中配置了listen指令,那么它的值将会设置为1,否则的话就代表没有配置listen指令,那么ngx就必须为当前server设置默认的监听信息,默认为80端口和所有ip地址。
(3)named_locations:其是一个动态数组,用来保存location的一些配置信息。
好了,接下来我们来看server命令的回调函数(ngx_http_core_server):
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //为配置上下文分配内存空间,这里之所以要重新创建新的ngx_http_conf_ctx_t配置结构,是为了处理同时会有好几个server的情况,但是这些创建的ngx_http_conf_ctx_t结构的main_conf都是统一的 if (ctx == NULL) { return NGX_CONF_ERROR; } http_ctx = cf->ctx; ctx->main_conf = http_ctx->main_conf; //这里用当前刚刚创建的配置上下文的main_conf指向以前的main_conf,这样可以保证main_conf的统一,因为main_conf只有一个
首先就是重新创建ngx_http_conf_ctx_t结构,然后将其的main_conf中的数据设置为上一级ngx_http_conf_ctx_t结构中的main_conf,说白了就是在http块命令中创建的main_conf ,这也就保证了mian_conf的统一。
//创建当前server块的srv_conf的内存空间,因为同一个http里面可能会有多个server命令,因而要为每一个server指令都给所有的http模块进行配置 ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->srv_conf == NULL) { return NGX_CONF_ERROR; } /* the server{}'s loc_conf */ //为当前server指令内的loc_conf分配内存 ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->loc_conf == NULL) { return NGX_CONF_ERROR; } //还是因为有多个server的存在,所以这里要继续要为每一个模块创建srv_conf与loc_conf for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[i]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); //重新创建http模块的srv_conf结构 if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[ngx_modules[i]->ctx_index] = mconf; } if (module->create_loc_conf) { mconf = module->create_loc_conf(cf); //重新创建http模块的loc_conf if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf; } }
接下来的代码就是为刚刚创建的ngx_http_conf_ctx_t结构重新分配srv_conf以及loc_conf 的内存空间,并调用http模块的相应的函数来初始化它们。
cscf = ctx->srv_conf[ngx_http_core_module.ctx_index]; //得到刚刚为ngx_http_core_module模块创建的server配置结构ngx_http_core_srv_conf_t cscf->ctx = ctx; //将当前ngx_http_core_module模块创建的srv_conf结构的ctx指针指向当前创建的ngx_http_conf_ctx_t结构 cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; //获取ngx_http_core_module的main_conf,它有一个servers数组,用来保存所有的server配置项,这里要知道的是main_conf 只有一个 cscfp = ngx_array_push(&cmcf->servers); //将刚刚创建的创建的ngx_http_core_module的srv_conf结构,即ngx_http_core_srv_conf_t结构保存入ngx_http_core_module模块的main_conf里面的servers数组当中 if (cscfp == NULL) { return NGX_CONF_ERROR; } *cscfp = cscf; //赋值,这里相当于是真正的压入到数组当中去
接下来的代码首先获取刚刚创建的ngx_http_core_srv_conf_t结构,并将其的ctx域指向刚刚创建的ngx_http_conf_ctx_t结构,接着在获取ngx_http_core_main_conf_t结构,由前面的说明我们已经知道这个结构是唯一的,然后将刚刚创建的ngx_http_core_srv_conf_t保存到ngx_http_core_main_conf_t的servers数组当中去。
pcf = *cf; //这里还是备份cf结构,然后准备解析server块命令,这里之所以备份,是因为ngx_conf_parse函数式递归调用了,为了内层的不影响外层的 cf->ctx = ctx; //相当于是把新创建的配置向下文传入解析函数 cf->cmd_type = NGX_HTTP_SRV_CONF; //表示要解析的命令的类型 rv = ngx_conf_parse(cf, NULL); //开始解析server块命令,这里面还涉及到location指令 *cf = pcf;
接下来的代码就开始调用ngx_conf_parse函数来继续解析server块里面的命令了。
if (rv == NGX_CONF_OK && !cscf->listen) { //表示当前所属的server块命令并没有用listen指令来申明监听端口,那么nginx会安排一个默认的监听,80端口 ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); sin = &lsopt.u.sockaddr_in; sin->sin_family = AF_INET; #if (NGX_WIN32) sin->sin_port = htons(80); //默认80端口 #else sin->sin_port = htons((getuid() == 0) ? 80 : 8000); #endif sin->sin_addr.s_addr = INADDR_ANY; //接收任意网卡连接 lsopt.socklen = sizeof(struct sockaddr_in); lsopt.backlog = NGX_LISTEN_BACKLOG; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) lsopt.setfib = -1; #endif lsopt.wildcard = 1; (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr, NGX_SOCKADDR_STRLEN, 1); if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) { //调用ngx_http_add_listen方法加入监听socket return NGX_CONF_ERROR; } }
最后的代码就是判断当前server块命令是否配置了listen命令,如果没有的话那么就需要为它安排默认的监听结构信息。
接下来我们来看listen命令的回调函数(ngx_http_core_listen):
cscf->listen = 1; //表示当前的server配置已经进行了listen配置,如果不listen配置的话,那么会安排默认的端口监听 value = cf->args->elts; //获取解析出来的参数 ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; //获取传进来的地址(ip+port) u.listen = 1; u.default_port = 80; //默认端口 if (ngx_parse_url(cf->pool, &u) != NGX_OK) { //相当于是对u进行初始化,比如说ip地址,端口号等 if (u.err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in \"%V\" of the \"listen\" directive", u.err, &u.url); } return NGX_CONF_ERROR; }
首先是将当前server块的配置结构的listen域设置为1,表示当前server已经设置了监听,然后获取传进来的地址,也就是ip+port的字符串,然后用其来初始化ngx_url_t结构,解析出ip地址端口号等。
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); //清零监听配置结构 ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen); //为lsopt的地址结构赋值 //初始化监听套接字的配置 lsopt.socklen = u.socklen; //socket地址结构的长度 lsopt.backlog = NGX_LISTEN_BACKLOG; lsopt.rcvbuf = -1; //接收缓冲 lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) lsopt.setfib = -1; #endif lsopt.wildcard = u.wildcard; //将二进制的地址结构转换为文本的形式 (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr, NGX_SOCKADDR_STRLEN, 1);
然后根据地址信息来初始化监听结构ngx_http_listen_opt_t,接下来就是再根据listen指令传进来的其他的参数来初始化ngx_http_listen_opt_t结构,例如是否是default_server等。
if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) { //加入监听套接字,将会创建ngx_http_conf_port_t结构,并将其放入到ngx_http_core_main_conf_t的ports数组当中 return NGX_CONF_OK; }
最后调用ngx_http_add_listen函数根据ngx_http_listen_opt_t来创建ngx_http_conf_port_t结构,并将其保存到ngx_http_core_main_conf_t结构的ports数组当中。
接下来我们来看ngx_http_add_listen函数:
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); //获取ngx_http_core_module的main_conf 配置结构,这个配置结构是保持统一的 //初始化ngx_http_core_module的ports数组 if (cmcf->ports == NULL) { //如果其ports的数组还没有初始化 cmcf->ports = ngx_array_create(cf->temp_pool, 2, sizeof(ngx_http_conf_port_t)); if (cmcf->ports == NULL) { return NGX_ERROR; } } //获取网际地址结构 sa = &lsopt->u.sockaddr; //判断地址结构的类型 switch (sa->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = &lsopt->u.sockaddr_in6; p = sin6->sin6_port; break; #endif #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: p = 0; break; #endif default: /* AF_INET */ sin = &lsopt->u.sockaddr_in; //获取IPV4地址结构 p = sin->sin_port; //获取端口号 break; }
首先是获取ngx_http_core_main_conf_t结构,判断其的ports数组是否初始化,如果没有的话那么要对其进行初始化。然后根据传进来的ngx_http_listen_opt_t结构,获取地址以及端口等信息。
port = cmcf->ports->elts; //遍历ports数组,看要添加的端口号信息是否存在,如果已经存在的话,调用ngx_http_add_addresses函数将相应的地址信息加入到port上就可以的 //也就是要判断以前是否已经在该端口上进行了listen,如果有的话那么只需要给该端口再加上一个地址结构就可以了 for (i = 0; i < cmcf->ports->nelts; i++) { if (p != port[i].port || sa->sa_family != port[i].family) { continue; } /* a port is already in the port list */ //如果端口的信息已经存在于ports数组当中了,那么只需要添加一个地址结构就可以了 return ngx_http_add_addresses(cf, cscf, &port[i], lsopt); }
接下来就是便利ports数组中的所有元素,判断是否在同一个端口已经配置了监听信息,如果有的话,那么只需要调用ngx_http_add_addresses函数在其的ngx_http_conf_port_t结构上加入一个地址记性了。
port = ngx_array_push(cmcf->ports); if (port == NULL) { return NGX_ERROR; } //相当于是为刚刚压入数组的元素赋值 port->family = sa->sa_family; port->port = p; //端口号 port->addrs.elts = NULL; //将addres数组置空 //添加地址信息 return ngx_http_add_address(cf, cscf, port, lsopt); //为该端口添加地址结构
如果没有的话,那么就需要在ports数组中新建一个ngx_http_conf_port_t结构,并初始化其的一些基本信息,最后调用ngx_http_add_address为其添加地址结构。
我们首先来看ngx_http_add_addresses函数吧,它表示在当前端口已经配置了监听信息了,那么就只需要为其添加一些地址信息就可以了:
addr = port->addrs.elts; /** * 在port的addr数组中已经存在该地址时,直接将ngx_http_core_srv_conf_t * 结构添加到到addr对应的servers数组中。 */ for (i = 0; i < port->addrs.nelts; i++) { //比较地址是否相同 if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) { continue; } /* the address is already in the address list */ //将当前的ngx_http_core_srv_conf_t直接压入到addr的servers数组中就行了 if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) { return NGX_ERROR; } /* preserve default_server bit during listen options overwriting */ default_server = addr[i].opt.default_server; #if (NGX_HTTP_SSL) ssl = lsopt->ssl || addr[i].opt.ssl; #endif if (lsopt->set) { if (addr[i].opt.set) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate listen options for %s", addr[i].opt.addr); return NGX_ERROR; } addr[i].opt = *lsopt; } /* check the duplicate "default" server for this address:port */ if (lsopt->default_server) { //当前监听配置结构的server配置结构为默认server if (default_server) { //如果已经设置了默认server,那么就表示同一个地址上设置了多个默认server,那么可以报错了 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a duplicate default server for %s", addr[i].opt.addr); return NGX_ERROR; } default_server = 1; addr[i].default_server = cscf; //将该地址的默认server设置为当前的server配置结构 } addr[i].opt.default_server = default_server; //表示已经有默认server配置信息了 #if (NGX_HTTP_SSL) addr[i].opt.ssl = ssl; #endif return NGX_OK; } /* add the address to the addresses list that bound to this port */ //表示在当前端口并没有同样的地址监听,那么需要为当前端口新加入一个地址监听 return ngx_http_add_address(cf, cscf, port, lsopt);
函数根据当前的ngx_http_conf_port_t的addrs数组中的信息来判断在当前端口上是否有相同的地址在监听,如果有的话那么就只需要为其(ngx_http_conf_addr_t)添加一个server配置结构就可以了,然后在判断一些default的信息来初始化,否则的话还是需要调用ngx_http_add_address函数来为当前的ngx_http_conf_port_t结构加入新的监听地址。
接下来我们来看ngx_http_add_address:
static ngx_int_t ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) { ngx_http_conf_addr_t *addr; //创建一个地址配置结构 //如果当前的port配置结构的addrs数组为空,那么初始化它 if (port->addrs.elts == NULL) { if (ngx_array_init(&port->addrs, cf->temp_pool, 4, sizeof(ngx_http_conf_addr_t)) != NGX_OK) { return NGX_ERROR; } } addr = ngx_array_push(&port->addrs); //压入ngx_http_conf_port_t结构的addrs数组当中 if (addr == NULL) { return NGX_ERROR; } //为压入的地址结构赋值 addr->opt = *lsopt; //监听配置结构,包含了地址地址等信息 addr->hash.buckets = NULL; addr->hash.size = 0; addr->wc_head = NULL; addr->wc_tail = NULL; #if (NGX_PCRE) addr->nregex = 0; addr->regex = NULL; #endif addr->default_server = cscf; //设置默认的server,将当前的server配置结构设置为当前地址监听的默认server,因为调用ngx_http_add_address函数代表是第一次加入该地址监听 addr->servers.elts = NULL; return ngx_http_add_server(cf, cscf, addr); //该函数用于将当前的ngx_http_core_srv_conf_t结构压入到地质结构addr的servers数组中,表示当前的地址结构增加一个server配置结构 }
该函数相对比较简单,首先判断ngx_http_conf_port_t结构的addrs数组是否已经初始化了,然后在向addrs数组中压入一个ngx_http_conf_addr_t结构就可以了,然后对其进行一些初始化,最后在调用ngx_http_add_server函数向刚创建的ngx_http_conf_addr_t结构中加入一个ngx_http_core_srv_conf_t配置结构就可以了。
接下来我们来看ngx_http_add_server函数:
static ngx_int_t ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_conf_addr_t *addr) { ngx_uint_t i; ngx_http_core_srv_conf_t **server; //初始化地址结构的servers数组 if (addr->servers.elts == NULL) { if (ngx_array_init(&addr->servers, cf->temp_pool, 4, sizeof(ngx_http_core_srv_conf_t *)) != NGX_OK) { return NGX_ERROR; } } else { server = addr->servers.elts; for (i = 0; i < addr->servers.nelts; i++) { if (server[i] == cscf) { //表示为同一个地址监听设置了两次相同的server配置结构,那么就出错了 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a duplicate listen %s", addr->opt.addr); return NGX_ERROR; } } } server = ngx_array_push(&addr->servers); //压入数组 if (server == NULL) { return NGX_ERROR; } *server = cscf; // 赋值 return NGX_OK; }
这个函数还是很简单的,说白了就是为ngx_http_conf_addr_t结构的servers数组中添加一个ngx_http_core_srv_conf_t结构就可以了。
根据上面的信息我们就可以知道通过listen指令实际上是将信息组织成了如下的形式: