看到了后面才发现其实自己在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指令实际上是将信息组织成了如下的形式: