http监听socket的初始化

嗯,一个http服务器,起码得要有http的监听socket吧,嗯,这篇文章就讲nginx是如何初始化http监听socket的。首先我们先来看几个十分重要的配置结构:
//监听的配置信息,在ngx_http_core_main_conf_t结构的ports数组中将会保存所有的端口监听配置信息
typedef struct {
    ngx_int_t                  family;
    in_port_t                  port;  //端口号
    ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */  //数组,存放所有当前端口的地址结构
} ngx_http_conf_port_t;
在nginx的http部分的核心模块ngx_http_core_module的srv_conf配置结构ngx_http_core_main_conf_t中,有一个ports数组,其就是用来存储上述结构体的,nginx将会建立这个数组然后来创建监听socket。
//地址结构
typedef struct {
    ngx_http_listen_opt_t      opt;  //监听的配置结构

//一些相关的hash变量,例如servername与ngx_http_core_srv_conf_t
    ngx_hash_t                 hash;
    ngx_hash_wildcard_t       *wc_head;  
    ngx_hash_wildcard_t       *wc_tail;

#if (NGX_PCRE)
    ngx_uint_t                 nregex;
    ngx_http_server_name_t    *regex;
#endif

    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;  //这个地址的默认server
    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */ //该地址的所有对应的server
} ngx_http_conf_addr_t;
在ngx_http_conf_port_t结构的最后一个域addrs是一个数组,其保存的就是上述结构,
//监听的配置结构
typedef struct {
    union {
        struct sockaddr        sockaddr; //通用套接字地质结构
        struct sockaddr_in     sockaddr_in; //网际套接字地质结构
#if (NGX_HAVE_INET6)
        struct sockaddr_in6    sockaddr_in6;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
        struct sockaddr_un     sockaddr_un;
#endif
        u_char                 sockaddr_data[NGX_SOCKADDRLEN];
    } u;

    socklen_t                  socklen;  //地址结构长度

    unsigned                   set:1;
    unsigned                   default_server:1;
    unsigned                   bind:1;   //绑定标志
    unsigned                   wildcard:1;
#if (NGX_HTTP_SSL)
    unsigned                   ssl:1;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
    unsigned                   ipv6only:2;
#endif
    unsigned                   so_keepalive:2;

    int                        backlog;
    int                        rcvbuf;
    int                        sndbuf;
#if (NGX_HAVE_SETFIB)
    int                        setfib;
#endif
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
    int                        tcp_keepidle;
    int                        tcp_keepintvl;
    int                        tcp_keepcnt;
#endif

#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
    char                      *accept_filter;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
    ngx_uint_t                 deferred_accept;
#endif

    u_char                     addr[NGX_SOCKADDR_STRLEN + 1];
} ngx_http_listen_opt_t;

这个对应的是地址结构ngx_http_conf_addr_t的opt域。

好了,看完这些重要的配置结构,我们可以开始着手看如何初始化http监听了,我们知道在server块命令中,有两个重要的命令,server_name与listen,server_name命令用来实现虚拟主机的功能,用来设置每个server块的虚拟主机名。listen命令则用来设置监听socket的信息。

我们先来看server_name的回调函数ngx_http_core_server:

    value = cf->args->elts;  //获取解析出来的参数

    for (i = 1; i < cf->args->nelts; i++) {

        ch = value[i].data[0];

        if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.'))
            || (ch == '.' && value[i].len < 2))
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "server name \"%V\" is invalid", &value[i]);
            return NGX_CONF_ERROR;
        }

        if (ngx_strchr(value[i].data, '/')) {
            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                               "server name \"%V\" has suspicious symbols",
                               &value[i]);
        }

        sn = ngx_array_push(&cscf->server_names);  //相当于是在srv_conf 的server_names数组中分配一个元素的空间,用来保存当前的server_name
        if (sn == NULL) {
            return NGX_CONF_ERROR;
        }
        sn->server = cscf;//将当前的server_name的所属srv_conf设置

        if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
            sn->name = cf->cycle->hostname;

        } else { 
            sn->name = value[i];   //赋值
        }
上面是截取的主要代码,说白了就是根据解析出来的参数创建一个ngx_http_server_name_t结构,然后将其压入到当前server块命令创建的ngx_http_core_srv_conf_t结构的server_names数组当中。

嗯,接下来看listen命令的回调函数ngx_http_core_listen(这个就比较重要了):

    ngx_http_core_srv_conf_t *cscf = conf;  //获取当前的srv_conf结构,这个ngx_http_core_srv_conf_t为当前server自己创建的ngx_http_core_srv_conf_t

    ngx_str_t              *value, size;
    ngx_url_t               u;  //url结构
    ngx_uint_t              n;
    ngx_http_listen_opt_t   lsopt; //用来存储监听套接字的配置信息

    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块命令创建的ngx_http_core_srv_conf_t结构,然后用listen后面的参数(一般情况下是ip+端口号的形式)来初始化url结构的url参数,另外还创建了一个监听配置信息ngx_http_listen_opt_t结构。然后还调用了ngx_parse_url函数,通过ip地址,端口号等将url结构最终转化为socket地址结构等。
   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结构,接下来的代码还有一些对其的初始化,但是没什么意思,就不贴出来了,最后比较重要的一段代码是:
    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_conf_port_t结构,并且还会把它保存到ngx_http_core_main_conf_t结构的ports数组当中,这样nginx就能通过这个数组来创建监听了。

好了接下来看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;
        }
    }
首先是获取ngx_http_core_main_conf_t结构,这里需要注意的是,前面的文章我们已经说过了,虽然在server命令快中也会创建自己的ngx_http_conf_ctx_t结构,但是它们所指向的main_conf 都是统一的,因为这里的 ngx_http_core_main_conf_t结构其实也是唯一的。接下来还要判断当前期的ports数组是否为空,如果为空的话那么还要初始化这个数组。
//获取网际地址结构
    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;
    }
接下来的代码是从lsopt中获取通用套接字地址结构,然后判断类型,并获取最终的IPV4地址结构以及端口号。
    port = cmcf->ports->elts;
	//遍历ports数组,看要添加的端口号信息是否存在,如果已经存在的话,调用ngx_http_add_addresses函数将相应的地址信息加入到port上就可以的
    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);
    }
	
接下来是遍历 ngx_http_core_main_conf_t的ports数组中的所有ngx_http_conf_port_t结构,看是否已经有相同的端口的信息了,如果有的话,那么只需要调用ngx_http_add_addresses函数来在当前ngx_http_conf_port_t结构中添加一个地址信息就可以了。如果没有相同的端口的信息的话,那么就需要以下的代码在ports数组中添加一个ngx_http_conf_port_t结构。
//表示要添加的端口信息并没有存在,那么需要在ports数组中加入一个元素,并进行初始化,并还要添加地址信息
    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);  //为该端口添加地址结构
上述代码就是用来添加一个 ngx_http_conf_port_t结构,然后最后依然要调用ngx_http_add_address为其添加地址结构。嗯,接下来我们先来看ngx_http_add_addresses函数:
 * 在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;
        }
首先是循环ports的addrs数组,找到是否有地址相同的,如果有的话,那么就好办了,只用调用ngx_http_add_server函数,将当前的srv_conf结构压入到addr的servers数组就可以了。当然如果最后找不到相同的地址的话,其实还是要调用ngx_http_add_address函数,在port的addrs数组添加一项。

接下来来看ngx_http_add_address函数吧:

/*
 * add the server address, the server names and the server core module
 * configurations to the port list
 */
//添加地址信息,该函数用于创建一个地址结构ngx_http_conf_addr_t,并将其加入到port的addrs数组当中,并初始化地址结构
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
    addr->servers.elts = NULL;

    return ngx_http_add_server(cf, cscf, addr);  //该函数用于将当前的ngx_http_core_srv_conf_t结构压入到地质结构addr的servers数组中,表示当前的地址结构增加一个server
}

该函数,一看代码应该就知道是什么意思了吧,说白了就是创建一个ngx_http_conf_addr_t地址结构,然后将其压入到当前port的addrs数组当中,然后当然还要对当前的地址结构进行一些初始化,最后调用ngx_http_add_server函数将当前的ngx_http_core_srv_conf_t压入到地址结构的servers数组当中就可以了。

嗯,上面写的很多了,我们来回顾一下监听初始化的解析配置文件的过程吧。

(1)调用listen命令的回调函数ngx_http_core_listen,根据解析配置文件获取的ip+port来创建监听套接字的配置结构ngx_http_listen_opt_t

(2)调用ngx_http_add_listen函数,判断当前ngx_http_core_main_conf_t结构中ports数组是否有相应端口的信息,如果已经有了的话,那么只需要调用ngx_http_add_addresses函数,在对应的ngx_http_conf_port_t结构中加入相应的地址信息就行了。如果没有的话那么就得创建一个ngx_http_conf_port_t结构,并且把其保存到ngx_http_core_main_conf_t的ports数组当中,然后再调用ngx_http_add_address函数为其添加地址信息。

(3)对于ngx_http_add_addresses以及ngx_http_add_address函数,其实说白了就是在ngx_http_conf_port_t结构的addrs数组中添加一个ngx_http_conf_addr_t结构,即地址结构,并且还会调用ngx_http_add_server函数为其配置虚拟主机的信息,nginx也会根据这个地址结构来创建监听socket。

最后,就是在ngx_http_core_main_conf_t结构中会有一个ports数组保存所有的ngx_http_conf_port_t结构,然后再ngx_http_conf_port_t结构中又会有一个addrs数组,保存端口对应的所有地址结构ngx_http_conf_addr_t(嗯,毕竟是服务器嘛,有几个网卡都是正常的)。接着对于一个ngx_http_conf_addr_t结构,又会有一个servers数组来保存其对应的所有虚拟主机的配置信息。

好了,监听部分的配置文件解析就差不多了,接下来可以开始具体如何创建监听结构,即ngx_listening_t结构了,在http命令的回调函数ngx_http_block最后会调用ngx_http_optimize_servers函数来完成这个过程。接下来我们就来看看这个函数吧。

//该函数相当于是遍历ngx_http_core_main_conf_t结构中的ports数组,然后创建监听,并还要将它们保存到cycle变量的listening数组当中去
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;
	//遍历ngx_http_core_main_conf_t结构中的ports数组中所有ngx_http_conf_port_t结构
    for (p = 0; p < ports->nelts; p++) {
		       /** 
				* 将addrs排序,带通配符的地址排在后面 
				*/	
        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++) {

            if (addr[a].servers.nelts > 1
#if (NGX_PCRE)
                || addr[a].default_server->captures
#endif
               )
            {
            /** 
                 * 初始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;
}
函数还是相对比较简单的,基本上注释也都说的比较清楚了,函数还会调用ngx_http_init_listening函数来具体的根据每一个ngx_http_conf_port_t结构来创建监听,接下来我们看看 ngx_http_init_listening函数吧。
//遍历ngx_http_conf_port_t结构addrs数组中的所有ngx_http_conf_addr_t结构,并根据它来创建监听
    while (i < last) {

        if (bind_wildcard && !addr[i].opt.bind) {
            i++;
            continue;
        }
//添加监听,即在cycle变量中添加一个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) {
            hport->naddrs = last;

        } else {
            hport->naddrs = 1;
            i = 0;
        }

        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) {
                return NGX_ERROR;
            }
            break;
        }

        addr++;
        last--;
    }
上述代码的主要内容就是通过遍历ngx_http_conf_port_t结构addrs数组中的所有ngx_http_conf_addr_t结构,并根据它来创建监听(调用ngx_http_add_listening函数),而且还要初始化ngx_listenting_t的servers域,用它来指明每一个监听socket的虚拟主机的信息。我们再来看看ngx_http_add_listening函数:
//该函数用于根据地址结构创建ngx_listening_t
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;
//创建一个监听,该监听结构会直接保存在cycle变量的listening数组当中去
    ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
    if (ls == NULL) {
        return NULL;
    }

    ls->addr_ntop = 1;

    ls->handler = ngx_http_init_connection;   //listen监听的处理函数,这里表示用这个函数来预处理刚刚accept的连接分配的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;
上述代码是截取的最主要的代码,说白了就是调用ngx_create_listening函数,在cycle的listening数组中添加一个ngx_listenting_t结构,然后还要初始化其的一些基本信息。并且还要将其的hander设置为ngx_http_init_connection函数,这个我们在前面讲事件循环的时候说到过,当监听端口获取一个连接,并为其分配connection之后,会调用这个handler来处理这个connection。

好了,这样子的话就创建了监听结构,并将其保存到了cycle中去了,然后http的监听socket初始化就完事了。至于最后是怎么打开socket的前面的文章已经说过了。

嗯,接下来的文章可以具体来分析http部分了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值