nginx listen指令淺析之add listen
微信公眾號:關注可瞭解更多的 Nginx
知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!
nginx listen指令淺析之add listen
前言
我們在上篇文章中介紹了 address:port
的解析過程,這篇文章繼續講解解析 listen
指令的後續過程。
解析 listen
指令的函式是 ngx_http_core_listen()
,這個函式的前半部分是解析 address:port
,我們在上篇文章中介紹過。後面緊接著就是解析各種 default_server
, recvbuf
等欄位,這些很簡單,只需要設定 ngx_http_listen_opt_t
結構體中的相應欄位即可,主要的功能在於下面的 ngx_http_add_listen()
函式。
函式功能
我們知道 ngx_http_core_main_conf_t
結構體中有一個 ports
欄位,這個欄位是一個 ngx_http_conf_port_t
型別的陣列。這個陣列儲存了當前 http
塊指令監聽的所有埠。 ngx_http_add_listen()
函式就是把監聽的埠資訊儲存到這個陣列中。
資料結構體
牽涉到的結構體如下:
埠資料結構:
1typedef struct { 2// 協議族型別,我們只討論IPV4,所以這裡是AF_INET 3ngx_int_tfamily; 4// 監聽的埠號 5in_port_tport; 6/* array of ngx_http_conf_addr_t */ 7ngx_array_taddrs; 8} ngx_http_conf_port_t; 複製程式碼
address
資料結構:
1typedef struct { 2ngx_http_listen_opt_topt; 3 4ngx_hash_thash; 5// 下面三個欄位都是雜湊表,用來儲存當前address:port對應的server_name 6// 其中key是server_name對應的字串 7// value 是 ngx_http_core_srv_conf_t 結構體 8ngx_hash_wildcard_t*wc_head; 9ngx_hash_wildcard_t*wc_tail; 10 11ngx_uint_tnregex; 12ngx_http_server_name_t*regex; 13 14/* the default server configuration for this address:port */ 15ngx_http_core_srv_conf_t*default_server; 16/* array of ngx_http_core_srv_conf_t */ 17ngx_array_tservers; 18} ngx_http_conf_addr_t; 複製程式碼
原始碼分析
下面的 ngx_http_add_listen()
原始碼刪除了 IPV6
和 Unix Domain
程式碼,只保留了 IPV4
相關的程式碼,如下:
1ngx_int_t 2ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, 3ngx_http_listen_opt_t *lsopt) 4{ 5in_port_tp; 6ngx_uint_ti; 7struct sockaddr*sa; 8struct sockaddr_in*sin; 9ngx_http_conf_port_t*port; 10ngx_http_core_main_conf_t*cmcf; 11 12cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 13 14// 為 ngx_http_core_main_conf_t 結構的 port 分配空間,儲存所有的埠 15if (cmcf->ports == NULL) { 16cmcf->ports = ngx_array_create(cf->temp_pool, 2, 17sizeof(ngx_http_conf_port_t)); 18} 19 20sa = &lsopt->u.sockaddr; 21 22switch (sa->sa_family) { 23 24default: /* AF_INET */ 25sin = &lsopt->u.sockaddr_in; 26p = sin->sin_port; // 拿到當前listen指令的埠號 27break; 28} 29 30port = cmcf->ports->elts; 31// 遍歷所有的port,檢視該埠號是否已經存在 32for (i = 0; i < cmcf->ports->nelts; i++) { 33// 這裡的意思就是:只有當協議版本和埠號都相同的話才算是同一個埠號 34if (p != port[i].port || sa->sa_family != port[i].family) { 35continue; 36} 37 38/* a port is already in the port list */ 39// 如果執行到這裡,說明之前已經存在了一個協議版本和埠號都相同的listen,這樣的話我們應該把當前監聽的 40// address 加入到已有的port->addrs 陣列中,統一管理當前埠對應的 address 41return 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// 表示一個我們監聽了一個新的埠 47port = ngx_array_push(cmcf->ports); 48if (port == NULL) { 49return NGX_ERROR; 50} 51 52port->family = sa->sa_family;// port中儲存了協議型別,因為我們區分不同的協議 53port->port = p; 54port->addrs.elts = NULL; 55// 將當前listen的address加入到port->addr中 56return 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, 4ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) 5{ 6ngx_http_conf_addr_t*addr; 7 8if (port->addrs.elts == NULL) { 9if (ngx_array_init(&port->addrs, cf->temp_pool, 4, 10sizeof(ngx_http_conf_addr_t)) 11!= NGX_OK) 12{ 13return NGX_ERROR; 14} 15} 16 17// 下面的程式碼就是將listen的address新增到port->addrs陣列中. 18// 然後初始化這個元素 19addr = ngx_array_push(&port->addrs); 20addr->opt = *lsopt; 21addr->hash.buckets = NULL; 22addr->hash.size = 0; 23addr->wc_head = NULL; 24addr->wc_tail = NULL; 25#if (NGX_PCRE) 26addr->nregex = 0; 27addr->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 32addr->default_server = cscf; 33addr->servers.elts = NULL; 34 // 下面的函式是將一個ngx_http_core_srv_conf_t 結構體新增到 addr->servers 中 35// 由於同一個 address:port 可以對應於多個server,所以這裡通過 port->addr->server欄位 36//將相同的 address:port 對應的所有虛擬主機關聯起來 37return ngx_http_add_server(cf, cscf, addr); 38} 複製程式碼
新增server
每個 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, 3ngx_http_conf_addr_t *addr) 4{ 5ngx_uint_ti; 6ngx_http_core_srv_conf_t**server; 7 8if (addr->servers.elts == NULL) { 9if (ngx_array_init(&addr->servers, cf->temp_pool, 4, 10sizeof(ngx_http_core_srv_conf_t *)) 11!= NGX_OK) 12{ 13return NGX_ERROR; 14} 15 16} else { 17server = addr->servers.elts; 18for (i = 0; i < addr->servers.nelts; i++) { 19if (server[i] == cscf) { 20// 這一部分的意思就是:防止同一個server塊內有兩個listen指令 21ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 22"a duplicate listen %s", addr->opt.addr); 23return NGX_ERROR; 24} 25} 26} 27 28// 下面的程式碼實在是沒有什麼意思,就是將 listen 指令所在的 ngx_http_core_srv_conf_t 29// 結構體新增到 addr->servers 陣列中 30server = ngx_array_push(&addr->servers); 31*server = cscf; 32 33return 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, 3ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) 4{ 5u_char*p; 6size_tlen, off; 7ngx_uint_ti, default_server; 8struct sockaddr*sa; 9ngx_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 19sa = &lsopt->u.sockaddr; 20 21switch (sa->sa_family) { 22 23default: /* AF_INET */ 24// off 欄位指向了 sockaddr_in 中 sin_addr 的起始地址 25// len 欄位說明了 sin_addr 的長度 26// 下面我們要比較相同的address是否已經存在了 27off = offsetof(struct sockaddr_in, sin_addr); 28len = 4;// sin_addr的長度 29break; 30} 31 32p = lsopt->u.sockaddr_data + off; 33 34addr = port->addrs.elts; 35 36for (i = 0; i < port->addrs.nelts; i++) { 37 // 遍歷當前port所有的addrs,查詢是否存在相同 address 38if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) { 39continue; 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// 陣列中即可 46if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) { 47return NGX_ERROR; 48} 49 50/* preserve default_server bit during listen options overwriting */ 51// 看一下當前遍歷到的listen指令是否設定了 default_server 52default_server = addr[i].opt.default_server; 53 54if (lsopt->set) { 55// 這裡的作用我們在 關於listen指令中的bind和set.note 文章中已經說過了 56if (addr[i].opt.set) { 57ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 58"duplicate listen options for %s", addr[i].opt.addr); 59return NGX_ERROR; 60} 61 62addr[i].opt = *lsopt; 63} 64 65/* check the duplicate "default" server for this address:port */ 66 67if (lsopt->default_server) { 68 69if (default_server) { 70ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 71"a duplicate default server for %s", addr[i].opt.addr); 72return NGX_ERROR; 73} 74 75default_server = 1; 76addr[i].default_server = cscf; 77} 78 79addr[i].opt.default_server = default_server; 80return NGX_OK; 81} 82 83/* add the address to the addresses list that bound to this port */ 84// 如果執行到這裡,說明該address尚不存在,所以新建一個address 85return ngx_http_add_address(cf, cscf, port, lsopt); 86} 複製程式碼
函式的關係
上面的幾個函式錯綜複雜,容易弄混,我對這幾個函式的功能進行了一些總結。
ngx_http_add_addresses()
函式的功能呢:
遍歷當前埠 addrs
陣列的所有元素, 檢視是否有 addrs
元素和當前的 address
相同
1、如果不存在相同的 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
陣列中。
2、如果存在相同的 address
元素,那麼就呼叫 ngx_http_add_server()
函式完成功能。
測試用例
該配置檔案僅用於測試 listen
指令
1user root; 2daemon on; 3worker_processes1; 4master_process on; 5 6events { 7use epoll; 8worker_connections1024; 9multi_accept on; 10accept_mutex on; 11} 12 13 14http{ 15 16# server_1,和server_2監聽的 ip:port 相同 17server { 18listen127.0.0.1:8088; 19server_name first; 20location / { 21rootfirst; 22} 23} 24 25# server_2,和server_1監聽的 ip:port 相同 26server { 27listen127.0.0.1:8088; 28server_name second; 29location / { 30rootsecond; 31} 32} 33 34# 先配置一個虛擬ip: ifconfig eth0:1 192.168.10.5 netmask 255.255.255.0 35# server_3,和server_1監聽的 port 相同,但是ip不同 36server { 37listen192.168.10.5:8088; 38server_name third; 39location / { 40rootthird; 41} 42 43} 44 45# server_4,和server_3監聽的 ip:port 相同 46server { 47listen192.168.10.5:8088; 48server_name fourth; 49location / { 50rootfourth; 51} 52 53} 54 55 56# server_5 57server { 58listen127.0.0.1:8089; 59server_name fiveth; 60location /{ 61rootfiveth; 62} 63 64} 65 66# server_6 與server_5監聽的埠相同,ip不同 67server { 68listen192.168.10.5:8089; 69server_name sixth; 70location /{ 71root sixth; 72} 73 74} 75 76 77# server_7 與server_5監聽的埠相同,但是監聽全部ip 78server { 79listen8089; 80server_name seventh; 81location / { 82rootseventh; 83} 84} 85} 複製程式碼
測試結果

喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達
