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:;
  2. listen 127.0.0.1;
  3. listen ;
  4. listen *:;
  5. listen localhost:;

  IPv6地址用方括号来表示:

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

  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 ~^www\d+\.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 = ;
  6. value = cf->args->elts;
  7. ngx_memzero(&u, sizeof(ngx_url_t));
  8.  
  9. u.url = value[];
  10. u.listen = ;
  11. u.default_port = ;
  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, ,
  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, ,
  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 = ; 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 = ; i < port->addrs.nelts; i++) {
  11. if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != ) {
  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, ,
  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, ,
  33. "a duplicate default server for %s", addr[i].opt.addr);
  34. return NGX_ERROR;
  35. }
  36. default_server = ;
  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, ,
  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 = ;
  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 = ; 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 = ; 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 = ; 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 = ; s < addr->servers.nelts; s++) {
  11. //每个server_name后面会带有多个域名
  12. name = cscfp[s]->server_names.elts;
  13. for (n = ; 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 - ].opt.wildcard) {
  8. addr[last - ].opt.bind = ;
  9. bind_wildcard = ;
  10.  
  11. } else {
  12. bind_wildcard = ;
  13. }
  14. i = ;
  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 - ) { //将*:port和没有显式bind的address:port放在同一个listen中
  30. hport->naddrs = last;
  31. } else {
  32. hport->naddrs = ;
  33. i = ; //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 = ;
  6. ...
  7. log = cycle->log;
  8. //尝试5次
  9. for (tries = ; tries; tries--) {
  10. failed = ;
  11. ls = cycle->listening.elts;
  12. for (i = ; i < cycle->listening.nelts; i++) {
  13. ...
  14. //创建socket
  15. s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, );
  16. ... //设置socket
  17. //绑定socket
  18. if (bind(s, ls[i].sockaddr, ls[i].socklen) == -) {
  19. ...
  20. }
  21. ...
  22. //设置socket为监听套接字
  23. if (listen(s, ls[i].backlog) == -) {
  24. ...
  25. }
  26. ls[i].listen = ;
  27. ls[i].fd = s;
  28. }
  29.  
  30. if (!failed) {
  31. break;
  32. }
  33. }
  34. ...
  35. }

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

nginx源码分析之网络初始化的更多相关文章

  1. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  2. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  3. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  4. ThinkPHP6源码分析之应用初始化

    ThinkPHP6 源码分析之应用初始化 官方群点击此处. App Construct 先来看看在 __construct 中做了什么,基本任何框架都会在这里做一些基本的操作,也就是从这里开始延伸出去 ...

  5. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

  6. nginx源码分析--使用GDB调试(strace、 pstack )

    nginx源码分析--使用GDB调试(strace.  pstack ) http://blog.csdn.net/scdxmoe/article/details/49070577

  7. SDL2源码分析1:初始化(SDL_Init())

    ===================================================== SDL源码分析系列文章列表: SDL2源码分析1:初始化(SDL_Init()) SDL2源 ...

  8. nginx源码分析-源码结构

    本文主要简单介绍nginx源码目录结构.程序编译流程.如何构建学习nginx的环境等.本文以及后续nginx源码分析文章是基于nginx当前(2009-02-27)的稳定版本0.6.35进行的分析,该 ...

  9. nginx源码分析——configure脚本

    源码:nginx 1.13.0-release   一.前言      在分析源码时,经常可以看到类似 #if (NGX_PCRE) .... #endif 这样的代码段,这样的设计可以在不改动源码的 ...

随机推荐

  1. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  2. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  3. IOS FMDB 获取数据库表和表中的数据

    ios开发中,经常会用到数据库sqlite的知识,除了增,删,改,查之外,我们说说如何获取数据库中有多少表和表相关的内容. 前言 跟数据库使用相关的一般的增删改查的语句,这里就不做解释了.在网上有很多 ...

  4. WebApi基于Token和签名的验证

    最近一段时间在学习WebApi,涉及到验证部分的一些知识觉得自己并不是太懂,所以来博客园看了几篇博文,发现一篇讲的特别好的,读了几遍茅塞顿开(都闪开,我要装逼了),刚开始读有些地方不理解,所以想了很久 ...

  5. jQuery个性化图片轮播效果

    jQuery个性化图片轮播效果 购物产品展示:图片轮播器<效果如下所示> 思路说明: 每隔一段时间,实现图片的自动切换及选项卡选中效果,鼠标划入图片动画停止,划出或离开动画开始 两个区域: ...

  6. cesium核心类Viewer简介

    1.简单描述Viewer Viewer类是cesium的核心类,是地图可视化展示的主窗口,cesium程序应用的切入口,扮演必不可少的核心角色. 官网的英文解析如下: A base widget fo ...

  7. IIS启动失败,启动Windows Process Activation Service时,出现错误13:数据无效 ;HTTP 错误 401.2 - Unauthorized 由于身份验证头无效,您无权查看此页

    因为修改过管理员账号的密码后重启服务器导致IIS无法启动,出现已下异常 1.解决:"启动Windows Process Activation Service时,出现错误13:数据无效&quo ...

  8. centos下彻底删除 和重装MYSQL

    1 删除Mysql      yum remove  mysql mysql-server mysql-libs mysql-server;       find / -name mysql 将找到的 ...

  9. [转]Java常用工具类集合

    转自:http://blog.csdn.net/justdb/article/details/8653166 数据库连接工具类——仅仅获得连接对象 ConnDB.java package com.ut ...

  10. KVM安装部署

    KVM安装部署 公司开始部署KVM,KVM的全称是kernel base virtual machine,对KVM虚拟化技术研究了一段时间, KVM是基于硬件的完全虚拟化,跟vmware.xen.hy ...