现在的位置: 首页 > 综合 > 正文

Nginx的server块listen命令详解

2014年07月03日 ⁄ 综合 ⁄ 共 12314字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.