微信公衆號:鄭爾多斯
關注可瞭解更多的Nginx
知識。任何問題或建議,請公衆號留言;
關注公衆號,有趣有內涵的文章第一時間送達!前端
咱們在上篇文章中介紹了address:port
的解析過程,這篇文章繼續講解解析listen
指令的後續過程。
解析listen
指令的函數是 ngx_http_core_listen()
,這個函數的前半部分是解析address:port
,咱們在上篇文章中介紹過。後面緊接着就是解析各類 default_server
, recvbuf
等字段,這些很簡單,只須要設置ngx_http_listen_opt_t
結構體中的相應字段便可,主要的功能在於下面的 ngx_http_add_listen()
函數。nginx
咱們知道ngx_http_core_main_conf_t
結構體中有一個ports
字段,這個字段是一個ngx_http_conf_port_t
類型的數組。這個數組保存了當前http
塊指令監聽的全部端口。ngx_http_add_listen()
函數就是把監聽的端口信息保存到這個數組中。數組
牽涉到的結構體以下:
端口數據結構:微信
1typedef struct {
2 // 協議族類型,咱們只討論IPV4,因此這裏是AF_INET
3 ngx_int_t family;
4 // 監聽的端口號
5 in_port_t port;
6 /* array of ngx_http_conf_addr_t */
7 ngx_array_t addrs;
8} ngx_http_conf_port_t;
複製代碼
address
數據結構:數據結構
1typedef struct {
2 ngx_http_listen_opt_t opt;
3
4 ngx_hash_t hash;
5// 下面三個字段都是哈希表,用來保存當前address:port對應的server_name
6// 其中key是server_name對應的字符串
7// value 是 ngx_http_core_srv_conf_t 結構體
8 ngx_hash_wildcard_t *wc_head;
9 ngx_hash_wildcard_t *wc_tail;
10
11 ngx_uint_t nregex;
12 ngx_http_server_name_t *regex;
13
14 /* the default server configuration for this address:port */
15 ngx_http_core_srv_conf_t *default_server;
16 /* array of ngx_http_core_srv_conf_t */
17 ngx_array_t servers;
18} ngx_http_conf_addr_t;
複製代碼
下面的ngx_http_add_listen()
源碼刪除了IPV6
和Unix Domain
代碼,只保留了IPV4
相關的代碼,以下:app
1ngx_int_t
2ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
3 ngx_http_listen_opt_t *lsopt)
4{
5 in_port_t p;
6 ngx_uint_t i;
7 struct sockaddr *sa;
8 struct sockaddr_in *sin;
9 ngx_http_conf_port_t *port;
10 ngx_http_core_main_conf_t *cmcf;
11
12 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
13
14// 爲 ngx_http_core_main_conf_t 結構的 port 分配空間,保存全部的端口
15 if (cmcf->ports == NULL) {
16 cmcf->ports = ngx_array_create(cf->temp_pool, 2,
17 sizeof(ngx_http_conf_port_t));
18 }
19
20 sa = &lsopt->u.sockaddr;
21
22 switch (sa->sa_family) {
23
24 default: /* AF_INET */
25 sin = &lsopt->u.sockaddr_in;
26 p = sin->sin_port; // 拿到當前listen指令的端口號
27 break;
28 }
29
30 port = cmcf->ports->elts;
31// 遍歷全部的port,查看該端口號是否已經存在
32 for (i = 0; i < cmcf->ports->nelts; i++) {
33 // 這裏的意思就是:只有當協議版本和端口號都相同的話纔算是同一個端口號
34 if (p != port[i].port || sa->sa_family != port[i].family) {
35 continue;
36 }
37
38 /* a port is already in the port list */
39// 若是執行到這裏,說明以前已經存在了一個協議版本和端口號都相同的listen,這樣的話咱們應該把當前監聽的
40// address 加入到已有的port->addrs 數組中,統一管理當前端口對應的 address
41 return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
42 }
43
44 /* add a port to the port list */
45 // 若是當前端口不存在,那麼就向 ngx_http_core_main_conf_t->port 中添加一個元素
46// 表示一個咱們監聽了一個新的端口
47 port = ngx_array_push(cmcf->ports);
48 if (port == NULL) {
49 return NGX_ERROR;
50 }
51
52 port->family = sa->sa_family;// port中保存了協議類型,由於咱們區分不一樣的協議
53 port->port = p;
54 port->addrs.elts = NULL;
55 // 將當前listen的address加入到port->addr中
56 return ngx_http_add_address(cf, cscf, port, lsopt);
57}
複製代碼
其實這個函數很簡單,具體能夠分爲如下幾個步驟:函數
ports
,查找是否存在相同協議類型和端口的元素ngx_http_add_addresses()
ngx_http_add_address()
上面的兩個函數名字也頗有意思,
ngx_http_add_addresses()
使用了複數形式的addresses()
,其實也說明了相同的元素已經存在,這裏須要添加多個元素。ngx_http_add_address()
使用單數形式的address
,說明添加的應該是第一個元素。源碼分析
上面咱們分析過,若是端口不存在,那麼會調用ngx_http_add_address()
函數,代碼以下:佈局
1//這個函數是將一個listen的addr加入到port->addrs數組中
2static ngx_int_t
3ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
4 ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
5{
6 ngx_http_conf_addr_t *addr;
7
8 if (port->addrs.elts == NULL) {
9 if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
10 sizeof(ngx_http_conf_addr_t))
11 != NGX_OK)
12 {
13 return NGX_ERROR;
14 }
15 }
16
17// 下面的代碼就是將listen的address添加到port->addrs數組中.
18// 而後初始化這個元素
19 addr = ngx_array_push(&port->addrs);
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#if (NGX_PCRE)
26 addr->nregex = 0;
27 addr->regex = NULL;
28#endif
29//先給default_server賦一個默認值,也便是當前address:port對應的ngx_http_core_srv_conf_t結構體
30// 下面的 ngx_http_add_server會根據 ngx_http_listen_opt_t 結構體的值從新給 default_server賦值.
31// 若是咱們在listen後面沒有設置default_server指令,那麼全部相同 address:port 的server的第一個server就是default_server
32 addr->default_server = cscf;
33 addr->servers.elts = NULL;
34 // 下面的函數是將一個ngx_http_core_srv_conf_t 結構體添加到 addr->servers 中
35// 因爲同一個 address:port 能夠對應於多個server,因此這裏經過 port->addr->server字段
36//將相同的 address:port 對應的全部虛擬主機關聯起來
37 return ngx_http_add_server(cf, cscf, addr);
38}
複製代碼
每一個address:port
均可能對應多個server
,ngx_http_add_server
函數就是server
和address:port
對應起來。測試
1static ngx_int_t
2ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
3 ngx_http_conf_addr_t *addr)
4{
5 ngx_uint_t i;
6 ngx_http_core_srv_conf_t **server;
7
8 if (addr->servers.elts == NULL) {
9 if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
10 sizeof(ngx_http_core_srv_conf_t *))
11 != NGX_OK)
12 {
13 return NGX_ERROR;
14 }
15
16 } else {
17 server = addr->servers.elts;
18 for (i = 0; i < addr->servers.nelts; i++) {
19 if (server[i] == cscf) {
20// 這一部分的意思就是:防止同一個server塊內有兩個listen指令
21 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
22 "a duplicate listen %s", addr->opt.addr);
23 return NGX_ERROR;
24 }
25 }
26 }
27
28// 下面的代碼實在是沒有什麼意思,就是將 listen 指令所在的 ngx_http_core_srv_conf_t
29// 結構體添加到 addr->servers 數組中
30 server = ngx_array_push(&addr->servers);
31 *server = cscf;
32
33 return NGX_OK;
34}
複製代碼
上面咱們說過,若是相同的端口已經存在,那麼就會調用ngx_http_add_addresses()
函數將當前的端口添加到相應的數組元素中。咱們下面看一下這個函數。
再次重申:執行
ngx_http_add_addresses
的時候,表示當前listen
的port
已經存在。
1static ngx_int_t
2ngx_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 u_char *p;
6 size_t len, off;
7 ngx_uint_t i, default_server;
8 struct sockaddr *sa;
9 ngx_http_conf_addr_t *addr;
10
11 /*
12 * we cannot compare whole sockaddr struct's as kernel
13 * may fill some fields in inherited sockaddr struct's.
14* 不能比較整個sockaddr結構體,由於內核可能在sockaddr中填充了其餘的字段。
15* 個人理解:不一樣版本的內核,sockaddr結構體中可能包含不一樣的字段,因此不能比較整個結構體,只能比較一些必然會存在的字段.
16* 這應該也是爲了兼容性
17 */
18
19 sa = &lsopt->u.sockaddr;
20
21 switch (sa->sa_family) {
22
23 default: /* AF_INET */
24// off 字段指向了 sockaddr_in 中 sin_addr 的起始地址
25// len 字段說明了 sin_addr 的長度
26// 下面咱們要比較相同的address是否已經存在了
27 off = offsetof(struct sockaddr_in, sin_addr);
28 len = 4;// sin_addr的長度
29 break;
30 }
31
32 p = lsopt->u.sockaddr_data + off;
33
34 addr = port->addrs.elts;
35
36 for (i = 0; i < port->addrs.nelts; i++) {
37 // 遍歷當前port全部的addrs,查找是否存在相同 address
38 if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) {
39 continue;
40 }
41
42 /* the address is already in the address list */
43 // 若是address已經存在,也就是說 address 和 port 所有相同
44// 那麼咱們就只需把 ngx_http_core_srv_conf_t 添加到 addrs->servers
45// 數組中便可
46 if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
47 return NGX_ERROR;
48 }
49
50 /* preserve default_server bit during listen options overwriting */
51// 看一下當前遍歷到的listen指令是否設置了 default_server
52 default_server = addr[i].opt.default_server;
53
54 if (lsopt->set) {
55// 這裏的做用咱們在 關於listen指令中的bind和set.note 文章中已經說過了
56 if (addr[i].opt.set) {
57 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
58 "duplicate listen options for %s", addr[i].opt.addr);
59 return NGX_ERROR;
60 }
61
62 addr[i].opt = *lsopt;
63 }
64
65 /* check the duplicate "default" server for this address:port */
66
67 if (lsopt->default_server) {
68
69 if (default_server) {
70 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
71 "a duplicate default server for %s", addr[i].opt.addr);
72 return NGX_ERROR;
73 }
74
75 default_server = 1;
76 addr[i].default_server = cscf;
77 }
78
79 addr[i].opt.default_server = default_server;
80 return NGX_OK;
81 }
82
83 /* add the address to the addresses list that bound to this port */
84 // 若是執行到這裏,說明該address尚不存在,因此新建一個address
85 return ngx_http_add_address(cf, cscf, port, lsopt);
86}
複製代碼
上面的幾個函數錯綜複雜,容易弄混,我對這幾個函數的功能進行了一些總結。
ngx_http_add_addresses()
函數的功能呢:
遍歷當前端口
addrs
數組的全部元素, 查看是否有addrs
元素和當前的address
相同
一、若是不存在相同的address
元素,則調用ngx_http_add_address()
完成以下功能:
- 將當前
listen
指令的address
做爲一個新元素添加到port->addrs
數組中- 調用
ngx_http_add_server()
函數完成以下功能
若是調用ngx_http_add_server()
則說明address
和port
都是相同的,那麼咱們應該將當前address:port
對應的ngx_http_core_srv_conf_t
結構體添加到port->addrs->servers
數組中。二、若是存在相同的
address
元素,那麼就調用ngx_http_add_server()
函數完成功能。
該配置文件僅用於測試listen
指令
1user root;
2daemon on;
3worker_processes 1;
4master_process on;
5
6events {
7 use epoll;
8 worker_connections 1024;
9 multi_accept on;
10 accept_mutex on;
11}
12
13
14http{
15
16 # server_1,和server_2監聽的 ip:port 相同
17 server {
18 listen 127.0.0.1:8088;
19 server_name first;
20 location / {
21 root first;
22 }
23 }
24
25 # server_2,和server_1監聽的 ip:port 相同
26 server {
27 listen 127.0.0.1:8088;
28 server_name second;
29 location / {
30 root second;
31 }
32 }
33
34 # 先配置一個虛擬ip: ifconfig eth0:1 192.168.10.5 netmask 255.255.255.0
35 # server_3,和server_1監聽的 port 相同,可是ip不一樣
36 server {
37 listen 192.168.10.5:8088;
38 server_name third;
39 location / {
40 root third;
41 }
42
43 }
44
45 # server_4,和server_3監聽的 ip:port 相同
46 server {
47 listen 192.168.10.5:8088;
48 server_name fourth;
49 location / {
50 root fourth;
51 }
52
53 }
54
55
56 # server_5
57 server {
58 listen 127.0.0.1:8089;
59 server_name fiveth;
60 location /{
61 root fiveth;
62 }
63
64 }
65
66 # server_6 與server_5監聽的端口相同,ip不一樣
67 server {
68 listen 192.168.10.5:8089;
69 server_name sixth;
70 location /{
71 root sixth;
72 }
73
74 }
75
76
77 # server_7 與server_5監聽的端口相同,可是監聽所有ip
78 server {
79 listen 8089;
80 server_name seventh;
81 location / {
82 root seventh;
83 }
84 }
85}
複製代碼
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達