http监听socket的初始化

来源:互联网 发布:csgo武器数据 编辑:程序博客网 时间:2024/06/06 02:31
嗯,一个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_tngx_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_tngx_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_tstatic 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部分了。

原创粉丝点击