zoukankan      html  css  js  c++  java
  • nginx源码分析之网络初始化

      nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化。

    从配置文件中读取初始化信息

      与网络有关的配置命令主要有两个:listen和sever_name。首先先了解这两个命令的用法。

    listen

      listen命令设置nginx监听地址,nginx从这里接受请求。对于IP协议,这个地址就是address和port;对于UNIX域套接字协议,这个地址就是path。 一条listen指令只能指定一个address或者port。 address也可以是主机名。 比如:

    1 listen 127.0.0.1:8000;
    2 listen 127.0.0.1;
    3 listen 8000;
    4 listen *:8000;
    5 listen localhost:8000;

      IPv6地址用方括号来表示:

    1 listen [::]:8000;
    2 listen [fe80::1];

      UNIX域套接字则使用“unix:”前缀:

    1 listen unix:/var/run/nginx.sock;

      如果只定义了address,nginx将使用80端口。在没有定义listen指令的情况下,如果以超级用户权限运行nginx,它将监听*:80,否则他将监听*:8000。如果listen指令携带default_server参数,当前虚拟主机将成为指定address:port的默认虚拟主机。 如果任何listen指令都没有携带default_server参数,那么第一个监听address:port的虚拟主机将被作为这个地址的默认虚拟主机。之所以会有默认虚拟主机,是由于同一个address:port可能会隶属于很多个虚拟主机,而区分这些虚拟主机则是用server_name指定各个虚拟主机的主机名。

      可以为listen指令定义若干额外的参数,这些参数用于套接字相关的系统调用。 这些参数可以在任何listen指令中指定,但是对于每个address:port,只能定义一次。具体参数看以到nginx帮助文档中查到,这里就不再说明了。

      更详细的介绍:http://nginx.org/cn/docs/http/ngx_http_core_module.html#listen

    server_name

      listen指令描述虚拟主机接受连接的地址和端口,用server_name指令列出虚拟主机的所有主机名。

      设置虚拟主机名,比如:

    1 server {
    2     server_name example.com www.example.com;
    3 }

      第一个名字成为虚拟主机的首要主机名。

      主机名中可以含有星号(“*”),以替代名字的开始部分或结尾部分:

    1 server {
    2     server_name example.com *.example.com www.example.*;
    3 }

      也可以在主机名中使用正则表达式,就是在名字前面补一个波浪线(“~”):

    1 server {
    2     server_name www.example.com ~^wwwd+.example.com$;
    3 }

      更详细的介绍:http://nginx.org/cn/docs/http/server_names.html

      了解了这两个命令的用法后,下面来看下源码中处理这两个命令的函数ngx_http_core_listen和ngx_http_core_server_name,这两个函数都在ngx_http_core_module.c文件中定义,也就是说这两个命令属于模块ngx_http_core_module。

     1 static char *
     2 ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
     3 {
     4     ...
     5     cscf->listen = 1;
     6     value = cf->args->elts;
     7     ngx_memzero(&u, sizeof(ngx_url_t));
     8 
     9     u.url = value[1];
    10     u.listen = 1;
    11     u.default_port = 80;
    12     //解析listen命令后面的参数,ip:port
    13     if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
    14         if (u.err) {
    15             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    16                                "%s in "%V" of the "listen" directive",
    17                                u.err, &u.url);
    18         }
    19         return NGX_CONF_ERROR;
    20     }
    21     //根据上面解析的参数初始化ngx_http_listen_opt_t结构
    22     ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
    23     ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen);
    24     lsopt.socklen = u.socklen;
    25     ...
    26     //解析其它参数,如default_server,bind等,并通过这些参数设置lsopt
    27     ...
    28     //将解析到的虚拟主机的地址信息加入到监听列表中
    29       if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
    30         return NGX_CONF_OK;
    31     }
    32     return NGX_CONF_ERROR;
    33 }

      从ngx_http_core_listen函数代码可以看出,ngx_http_add_listen函数是其主要的部分,下面看下ngx_http_add_listen:

     1 ngx_int_t
     2 ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
     3     ngx_http_listen_opt_t *lsopt)
     4 {
     5     ...
     6     //获取ngx_http_core_module的main配置结构
     7     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
     8     if (cmcf->ports == NULL) {  //初始化ports数组
     9         cmcf->ports = ngx_array_create(cf->temp_pool, 2,
    10                                        sizeof(ngx_http_conf_port_t));
    11         if (cmcf->ports == NULL) {
    12             return NGX_ERROR;
    13         }
    14     }
    15     sa = &lsopt->u.sockaddr; 
    16     ...  //解析协议
    17     port = cmcf->ports->elts;  //查看已经注册的port,是否新加入地址信息中的port已经存在了
    18     for (i = 0; i < cmcf->ports->nelts; i++) {
    19         if (p != port[i].port || sa->sa_family != port[i].family) {
    20             continue;
    21         }
    22         //port已经存在了,将地址信息加入到这个port的地址列表中
    23         return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
    24     }
    25     //port不存在,将新的port加入到ports数组中
    26     port = ngx_array_push(cmcf->ports);
    27     if (port == NULL) {
    28         return NGX_ERROR;
    29     }
    30     port->family = sa->sa_family;
    31     port->port = p;
    32     port->addrs.elts = NULL;
    33     return ngx_http_add_address(cf, cscf, port, lsopt);  //将地址信息加入到对应port的地址列表中,一个port可以对应过个地址
    34 }

      ngx_http_add_listen中调用了ngx_http_add_addresses和ngx_http_add_address函数,先看下ngx_http_add_addresses:

     1 static ngx_int_t
     2 ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
     3     ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
     4 {
     5     ...
     6     sa = &lsopt->u.sockaddr;
     7     ...
     8     p = lsopt->u.sockaddr_data + off;
     9     addr = port->addrs.elts;
    10     for (i = 0; i < port->addrs.nelts; i++) {
    11         if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) {
    12             continue;
    13         }
    14         //新加入的地址已经在地址列表中存在了,将新的虚拟主机信息加入到这个地址的虚拟主机列表中
    15         if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
    16             return NGX_ERROR;
    17         }
    18         default_server = addr[i].opt.default_server;
    19         if (lsopt->set) {  //新的虚拟主机信息中设置了其它参数
    20 
    21             if (addr[i].opt.set) {
    22                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    23                         "duplicate listen options for %s", addr[i].opt.addr);
    24                 return NGX_ERROR;
    25             }
    26 
    27             addr[i].opt = *lsopt;
    28         }
    29         if (lsopt->default_server) {  //新的虚拟主机被设置为默认主机
    30 
    31             if (default_server) {
    32                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    33                         "a duplicate default server for %s", addr[i].opt.addr);
    34                 return NGX_ERROR;
    35             }
    36             default_server = 1;
    37             addr[i].default_server = cscf;
    38         }
    39         addr[i].opt.default_server = default_server;
    40         ...
    41         return NGX_OK;
    42     }
    43     //添加新地址信息到port的地址列表中
    44     return ngx_http_add_address(cf, cscf, port, lsopt);
    45 }

      ngx_http_add_addresses函数中如果address:port都已经存在了,则调用ngx_http_add_server将新的虚拟主机的配置加入到address:port对应的虚拟主机列表中,由于一个address:port是可以对应多个虚拟主机的。如果address:port不存在,则调用ngx_http_add_address,将新的address加入到port地址列表中。下面看下ngx_http_add_address函数:

     1 ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
     2     ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
     3 {
     4     ngx_http_conf_addr_t  *addr;
     5     //初始化port地址列表
     6     if (port->addrs.elts == NULL) {
     7         if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
     8                            sizeof(ngx_http_conf_addr_t))
     9             != NGX_OK)
    10         {
    11             return NGX_ERROR;
    12         }
    13     }
    14     ...
    15     //将新地址加入到地址列表中
    16     addr = ngx_array_push(&port->addrs);
    17     if (addr == NULL) {
    18         return NGX_ERROR;
    19     }
    20     addr->opt = *lsopt;
    21     addr->hash.buckets = NULL;
    22     addr->hash.size = 0;
    23     addr->wc_head = NULL;
    24     addr->wc_tail = NULL;
    25     addr->default_server = cscf;
    26     addr->servers.elts = NULL;
    27     //将新的虚拟主机信息加入到这个地址的虚拟主机列表中
    28     return ngx_http_add_server(cf, cscf, addr);
    29 }

      这个函数代码很简单,初始化地址列表,并调用ngx_http_add_server将新的虚拟主机的配置加入到address:port对应的虚拟主机列表中。下面再看下ngx_http_add_server函数:

     1 static ngx_int_t
     2 ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
     3     ngx_http_conf_addr_t *addr)
     4 {
     5     ...
     6     server = ngx_array_push(&addr->servers);  //添加新的虚拟主机配置
     7     if (server == NULL) {
     8         return NGX_ERROR;
     9     }
    10     *server = cscf;
    11     return NGX_OK;
    12 }

      上面的对listen指令的处理函数基本分析完了,接下来再分析server_name指令对应的函数ngx_http_core_server_name:

     1 static char *
     2 ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
     3 {
     4     ...
     5     value = cf->args->elts;
     6     for (i = 1; i < cf->args->nelts; i++) {
     7         ...
     8         sn = ngx_array_push(&cscf->server_names);
     9         if (sn == NULL) {
    10             return NGX_CONF_ERROR;
    11         }
    12         ...
    13         sn->name = value[i];
    14     ...
    15 }

      这个函数主要是把server_name命令后面各个主机名放到当前虚拟主机配置的server_names数组中。

      分析到这里,已经将配置文件中所有虚拟主机配置信息都读取到ngx_http_core_module模块的配置信息的ports中。在http命令的处理函数ngx_http_block最后调用了函数ngx_http_optimize_servers对上面的配置信息做了优化,下面具体来看下这个函数:

     1 static ngx_int_t
     2 ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
     3     ngx_array_t *ports)
     4 {
     5     ...
     6     port = ports->elts;
     7     for (p = 0; p < ports->nelts; p++) {
     8 
     9         ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
    10                  sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
    11         addr = port[p].addrs.elts;
    12         for (a = 0; a < port[p].addrs.nelts; a++) {
    13         ...
    14             if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
    15                 return NGX_ERROR;
    16             }
    17         }
    18         if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
    19             return NGX_ERROR;
    20         }
    21     }
    22     return NGX_OK;
    23 }

      这个函数先对每个address:port调用ngx_http_server_names函数,然后对每个port调用ngx_http_init_listening函数。下面看看ngx_http_server_names函数:

     1 static ngx_int_t
     2 ngx_http_server_names(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
     3     ngx_http_conf_addr_t *addr)
     4 {
     5     ...
     6     if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
     7         goto failed;
     8     }
     9     cscfp = addr->servers.elts;
    10     for (s = 0; s < addr->servers.nelts; s++) {
    11         //每个server_name后面会带有多个域名
    12         name = cscfp[s]->server_names.elts;
    13         for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
    14             ...
    15             rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,
    16                                   NGX_HASH_WILDCARD_KEY);
    17             ...
    18             }
    19         }
    20     }
    21     ...
    22     if (ha.keys.nelts) {  //无通配
    23         hash.hash = &addr->hash;  //非通配hash
    24         hash.temp_pool = NULL;
    25 
    26         if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
    27             goto failed;
    28         }
    29     }
    30     if (ha.dns_wc_head.nelts) {  //前缀通配
    31         ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
    32                   sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
    33 
    34         hash.hash = NULL;  //使用通配hash
    35         hash.temp_pool = ha.temp_pool;
    36         if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
    37                                    ha.dns_wc_head.nelts)
    38         ...
    39         addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
    40     }
    41     if (ha.dns_wc_tail.nelts) {  //后缀通配
    42         ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
    43                   sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
    44         hash.hash = NULL;  //使用通配hash
    45         hash.temp_pool = ha.temp_pool;
    46         if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
    47                                    ha.dns_wc_tail.nelts)
    48         ...
    49         addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
    50     }
    51     ...
    52 }

      上面代码主要就是将每个address:port对应的所有域名与域名所在的虚拟主机配置信息建立hash映射,这样就可以通过域名快速找到域名所在的虚拟主机配置信息。有关nginx的hash可以参考nginx源码分析之hash的实现这篇文章。下面再看下ngx_http_init_listening函数:

     1 static ngx_int_t
     2 ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
     3 {
     4     ...
     5     addr = port->addrs.elts;
     6     last = port->addrs.nelts;
     7     if (addr[last - 1].opt.wildcard) {
     8         addr[last - 1].opt.bind = 1;
     9         bind_wildcard = 1;
    10 
    11     } else {
    12         bind_wildcard = 0;
    13     }
    14     i = 0;
    15     while (i < last) {  //last代表的是address:port的个数
    16         //忽略隐式绑定
    17         if (bind_wildcard && !addr[i].opt.bind) {
    18             i++;
    19             continue;
    20         }
    21         //这个函数里面将会创建,并且初始化listen结构,这个listen已经存放在cycle结构的listen数组中
    22         ls = ngx_http_add_listening(cf, &addr[i]);
    23         if (ls == NULL) {
    24             return NGX_ERROR;
    25         }
    26         hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
    27         ...
    28         ls->servers = hport;
    29         if (i == last - 1) {  //将*:port和没有显式bind的address:port放在同一个listen中
    30             hport->naddrs = last;
    31         } else {
    32             hport->naddrs = 1;
    33             i = 0;  //i重新赋值为0
    34         }
    35         switch (ls->sockaddr->sa_family) {
    36         ...
    37         default: /* AF_INET */
    38             if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {  //初始化虚拟主机相关的地址,设置hash等等.
    39                 return NGX_ERROR;
    40             }
    41             break;
    42         }
    43         addr++;
    44         last--;
    45     }
    46     return NGX_OK;
    47 }

       这个函数就是遍历某个端口port对应的所有address,如果所有address中不包含通配符,则对所有的address:port调用ngx_http_add_listening分配一个listen结构和ngx_http_port_t结构,并初始化它们。如果存在address包含通配符,则如果address:port需要bind,分配一个listen结构和ngx_http_port_t结构,并初始化它们,对所有address:port不需要bind的,它们和包含通配符*:port共同使用一个listen结构和ngx_http_port_t结构,并且listen结构中包含的地址是*:port,所以最好bind的地址是*:port。所有的listen都会存放在全局变量ngx_cycle的listening数组中,这样后面就可以利用这些address:port信息建立每个套接字了。

    建立监听套接字

      建立监听套接字是在ngx_open_listening_sockets中完成,这个函数是在ngx_init_cycle中被调用的。

     1 ngx_int_t
     2 ngx_open_listening_sockets(ngx_cycle_t *cycle)
     3 {
     4     ...
     5     reuseaddr = 1;
     6     ...
     7     log = cycle->log;
     8     //尝试5次
     9     for (tries = 5; tries; tries--) {
    10         failed = 0;
    11         ls = cycle->listening.elts;
    12         for (i = 0; i < cycle->listening.nelts; i++) {
    13             ...
    14             //创建socket
    15             s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
    16             ...  //设置socket
    17             //绑定socket
    18             if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
    19                 ...
    20             }
    21             ...
    22             //设置socket为监听套接字
    23             if (listen(s, ls[i].backlog) == -1) {
    24                 ...
    25             }
    26             ls[i].listen = 1;
    27             ls[i].fd = s;
    28         }
    29 
    30         if (!failed) {
    31             break;
    32         }
    33     }
    34     ...
    35 }

      这个函数就是遍历listening数组,为每个listen结构创建监听套接字。到目前为止,所有的网络初始化部分就基本完成了,然后就是根据这些监听套接字来获取客户端的连接请求,并处理这些请求了。怎样获取客户端连接和nginx的进程模型和事件处理有关,进程模型和事件处理后面再贴文章分析。

  • 相关阅读:
    CodeForces 1332D Walk on Matrix
    CodeForces 1324F Maximum White Subtree
    CodeForces-1324E-Sleeping-Schedule
    CodeForces-1324D-Pair-of-Topics
    理解字节序 大端字节序和小端字节序
    公钥 私钥 数字证书概念理解
    简要介绍 X Window System (又称为X11 or X)
    动态规划的简洁说明
    二分法的研究
    两个变量的内容交换
  • 原文地址:https://www.cnblogs.com/chengxuyuancc/p/3789216.html
Copyright © 2011-2022 走看看