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 ~^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 = 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的進程模型和事件處理有關,進程模型和事件處理后面再貼文章分析。
總結
以上是生活随笔為你收集整理的nginx源码分析之网络初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx源码分析—数组结构ngx_ar
- 下一篇: ngx_connection_t结构体