微信公衆號:鄭爾多斯
關注可瞭解更多的Nginx
知識。任何問題或建議,請公衆號留言;
關注公衆號,有趣有內涵的文章第一時間送達!前端
咱們在前面介紹了listen
和server_name
指令的處理過程,下面咱們繼續對這兩個指令進行分析。nginx
的http
指令的處理函數爲ngx_http_block()
,在該函數的最後有會調用ngx_http_optimize_servers()
函數對listen
指令和server_name
指令的處理結果進行優化,本文的目的就是分析這個優化過程。nginx
首先咱們查看ngx_http_block()
函數的最後面有以下代碼:正則表達式
1 if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
2 return NGX_CONF_ERROR;
3 }
複製代碼
這裏調用了ngx_http_optimize_servers()
函數對listen
指令的結果進行優化。數組
下面的代碼我刪除了一些錯誤判斷等處理代碼,只關注主流程。微信
1// @params cmcf: 全局的ngx_http_core_main_conf_t結構體
2// @params ports: 保存ports信息的數組
3static ngx_int_t
4ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
5 ngx_array_t *ports)
6{
7 ngx_uint_t p, a;
8 ngx_http_conf_port_t *port;
9 ngx_http_conf_addr_t *addr;
10
11 port = ports->elts;
12 // 遍歷全部的端口,逐個處理每一個端口
13 for (p = 0; p < ports->nelts; p++) {
14// 對 port->addrs 數組中的每一個 addr 結構進行排序
15// 排序規則下文有分析
16 ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
17 sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
18
19/*
20 check whether all name-based servers have the same configuraiton as a default server for given address:port
21*/
22 addr = port[p].addrs.elts;
23 // 遍歷端口的每一個addrs數組元素,單獨處理
24 for (a = 0; a < port[p].addrs.nelts; a++) {
25 // 若是相同的 address:port 對應的server有多個,
26// 那麼要對這些server進行排序
27 if (addr[a].servers.nelts > 1
28 || addr[a].default_server->captures
29 )
30 {
31 if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
32 return NGX_ERROR;
33 }
34 }
35 }
36// 對當前port元素進行初始化
37//切記:一個port元素就是一個端口,咱們就要監聽一個
38// 該函數很重要,下篇文章專門分析這個函數
39 if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
40 return NGX_ERROR;
41 }
42 }
43
44 return NGX_OK;
45}
複製代碼
上面的代碼中遍歷全部的ports
數組元素,而後對每個ports
元素的addr
進行排序。排序函數是ngx_http_cmp_conf_addrs()
,代碼以下:app
1static ngx_int_t
2ngx_http_cmp_conf_addrs(const void *one, const void *two)
3{
4 ngx_http_conf_addr_t *first, *second;
5
6 first = (ngx_http_conf_addr_t *) one;
7 second = (ngx_http_conf_addr_t *) two;
8
9 if (first->opt.wildcard) {
10 /* a wildcard address must be the last resort, shift it to the end */
11 return 1;
12 }
13
14 if (second->opt.wildcard) {
15 /* a wildcard address must be the last resort, shift it to the end */
16 return -1;
17 }
18
19 if (first->opt.bind && !second->opt.bind) {
20 /* shift explicit bind()ed addresses to the start */
21 return -1;
22 }
23
24 if (!first->opt.bind && second->opt.bind) {
25 /* shift explicit bind()ed addresses to the start */
26 return 1;
27 }
28
29 /* do not sort by default */
30
31 return 0;
32}
複製代碼
排序後的規則以下:函數
- 若是
first addr
是wildcard
,那麼這個addr
排在後面second
的後面,這一句就說明wildcard
屬性要排在全部的地址的後面。- 若是
first addr
不是wildcard
,可是second
是wildcard
,那麼first
排在second
的前面,也就是說wildcard
類型的要排在 非wildcard
後面- 若是
first
和second
都不是wildcard
,可是first
有bind
屬性,而second
沒有bind
屬性,那麼first
排在second
的前面,這個規則說明bind
屬性要排在非bind
的前面。- 若是
first
和second
都不是wildcard
,可是first
沒有bind
屬性,而second
有bind
屬性,那麼frist
排在second
的後面,也是爲了說明bind
要排在前面。- 其餘狀況的排序規則不變
總而言之,
源碼分析
在ngx_http_optimize_servers
函數中,有下面一段代碼:優化
1 for (a = 0; a < port[p].addrs.nelts; a++) {
2 // 若是相同的 address:port 對應的server有多個,
3// 那麼要對這些server進行排序
4 if (addr[a].servers.nelts > 1
5 || addr[a].default_server->captures
6 )
7 {
8 if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
9 return NGX_ERROR;
10 }
11 }
12 }
複製代碼
這段代碼是遍歷一個port
端口,而後對當前端口的server
進行處理。代碼分析以下:ui
1ngx_http_server_names(
2ngx_conf_t *cf, // ngx_conf_t 結構體 配置文件的結構體
3ngx_http_core_main_conf_t *cmcf, //
4ngx_http_conf_addr_t *addr //每一個port下面的addr數組中的一個元素
5)
6{
7 ngx_int_t rc;
8 ngx_uint_t n, s;
9 ngx_hash_init_t hash;
10 ngx_hash_keys_arrays_t ha;
11 ngx_http_server_name_t *name;
12 ngx_http_core_srv_conf_t **cscfp;
13#if (NGX_PCRE)
14 ngx_uint_t regex, i;
15 regex = 0;
16#endif
17
18 ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
19
20 ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
21 if (ha.temp_pool == NULL) {
22 return NGX_ERROR;
23 }
24
25 ha.pool = cf->pool;
26
27 if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
28 goto failed;
29 }
30
31// cscfp 指向了addr下面的ngx_http_core_srv_conf_t 數組
32 cscfp = addr->servers.elts;
33 // 遍歷該addr下面的全部 ngx_http_core_srv_conf_t 數組
34 for (s = 0; s < addr->servers.nelts; s++) {
35 // ngx_http_core_srv_name中的server_names字段也是一個數組
36 name = cscfp[s]->server_names.elts;
37 // 這裏遍歷這個server_names數組
38 for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
39#if (NGX_PCRE)
40 if (name[n].regex) {
41 regex++;
42 continue;
43 }
44#endif
45 // ngx_http_srv_conf_t 結構體的server_names字段是一個
46// ngx_http_server_name_t 數組,這個結構體有一個server字段
47// 該字段指向server_name指令所在的ngx_http_core_srv_conf_t結構體
48// 下面的指令是將 server_name 和 這個server_name 所在的
49// ngx_http_core_srv_name_t 結構體做爲 <key, value> 鍵值對保存到
50// hash表中。這樣經過server_name 就能夠快速找到對應的
51// ngx_http_core_srv_conf_t 結構體
52// 下面的函數是將server name和對應的ngx_http_core_srv_conf_t結構
53// 體都添加到ngx_hash_keys_arrays_t數組中,
54// 爲下面的hash初始化作準備
55 rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, NGX_HASH_WILDCARD_KEY);
56
57 //咱們刪除了一些錯誤處理的代碼,只關注主流程
58
59 }
60 }
61
62 hash.key = ngx_hash_key_lc;
63 hash.max_size = cmcf->server_names_hash_max_size;
64 hash.bucket_size = cmcf->server_names_hash_bucket_size;
65 hash.name = "server_names_hash";
66 hash.pool = cf->pool;
67 // ha.keys 保存了不含通配符的hash表
68 if (ha.keys.nelts) {
69 hash.hash = &addr->hash;
70 hash.temp_pool = NULL;
71
72 if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
73 goto failed;
74 }
75 }
76 // ha.dns_wc_head 保存了包含前綴符的哈希表
77 if (ha.dns_wc_head.nelts) {
78 // 先對hash表中的key進行排序,而後在進行初始化
79 ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
80 sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
81
82 hash.hash = NULL;
83 hash.temp_pool = ha.temp_pool;
84
85 if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
86 ha.dns_wc_head.nelts)
87 != NGX_OK)
88 {
89 goto failed;
90 }
91
92 addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
93 }
94 // ha.dns_wc_tail 包含了後綴符的哈希表
95 if (ha.dns_wc_tail.nelts) {
96 // 先對hash表中的key進行排序,而後在進行初始化
97 ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
98 sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
99
100 hash.hash = NULL;
101 hash.temp_pool = ha.temp_pool;
102
103 if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
104 ha.dns_wc_tail.nelts)
105 != NGX_OK)
106 {
107 goto failed;
108 }
109
110 addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
111 }
112
113 ngx_destroy_pool(ha.temp_pool);
114
115#if (NGX_PCRE)
116 //若是包含了正則匹配,那麼 addr->nregex 保存了正在匹配的server的數量
117 if (regex == 0) {
118 return NGX_OK;
119 }
120
121 addr->nregex = regex;
122 addr->regex = ngx_palloc(cf->pool, regex * sizeof(ngx_http_server_name_t));
123 if (addr->regex == NULL) {
124 return NGX_ERROR;
125 }
126
127 i = 0;
128
129 for (s = 0; s < addr->servers.nelts; s++) {
130
131 name = cscfp[s]->server_names.elts;
132
133 for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
134 if (name[n].regex) {
135 addr->regex[i++] = name[n];
136 }
137 }
138 }
139
140#endif
141
142 return NGX_OK;
143
144failed:
145
146 ngx_destroy_pool(ha.temp_pool);
147
148 return NGX_ERROR;
149}
複製代碼
上面的函數很簡單,咱們總結一下就好了:
使用到的結構體以下所示:
1typedef struct {
2 ngx_http_listen_opt_t opt;//當前address:port對應的listen配置項
3 // hash, wc_head, wc_tail 這三個哈希表的key都是server_name, value是對應的ngx_http_core_srv_conf_t結構體
4 ngx_hash_t hash;
5 ngx_hash_wildcard_t *wc_head;
6 ngx_hash_wildcard_t *wc_tail;
7
8#if (NGX_PCRE)
9 ngx_uint_t nregex;
10 ngx_http_server_name_t *regex;
11#endif
12
13 /* the default server configuration for this address:port */
14 ngx_http_core_srv_conf_t *default_server;
15// servers是一個數組,它用來保存當前address:port對應的全部ngx_http_core_srv_conf_t結構體。
16// 在ngx_http_servers() 函數處理以後,咱們能夠從hash, wc_head, wc_tail中方便的獲取server name以及對應的ngx_http_core_srv_conf_t
17 ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */
18} ngx_http_conf_addr_t;
複製代碼
opt
字段保存了ngx_http_listen_opt_t
結構體,這個結構體包含了當前端口的一些配置屬性。
hash
字段保存了不包含通配符的server_name
,ngx_http_core_srv_conf_t
組成的鍵值對。
wc_head
字段:包含前綴通配符的server_name
,ngx_http_core_srv_conf_t
組成的鍵值對。
wc_tail
字段:包含後綴通配符的server_name
,ngx_http_core_srv_conf_t
組成的鍵值對。
nregex
字段:包含正則匹配的ngx_http_core_srv_conf_t
的數量
regex
字段:這是一個數組,數組元素爲server_name
包含正則表達式的ngx_http_server_name_t
結構。數組的大小爲上述的nregex
字段的值。
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達