微信公眾號:鄭爾多斯
關注可瞭解更多的Nginx
知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!
前言
本篇文章詳細介紹一下listen
指令的解析,以及socket
的建立過程。listen
的內容太多了,並且牽涉到後面的很多地方,所以只能再一次的回到這裡仔細的學習listen
指令的解析過程。首先要參考listen
的nginx
官方文件,知道listen的用法,然後才能學習原始碼。
listen配置
我們先從原始檔中找到listen
的配置項,如下:
1{
2 ngx_string("listen"),
3 NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
4 ngx_http_core_listen,
5 NGX_HTTP_SRV_CONF_OFFSET,
6 0,
7 NULL
8}
複製程式碼
我們從配置檔案中可以看到,listen
指令的解析函式為ngx_http_core_listen
,我們下面分析一下這個函式。
使用到的結構體
1typedef struct {
2 union {
3 struct sockaddr sockaddr;
4 struct sockaddr_in sockaddr_in;
5#if (NGX_HAVE_INET6)
6 struct sockaddr_in6 sockaddr_in6;
7#endif
8#if (NGX_HAVE_UNIX_DOMAIN)
9 struct sockaddr_un sockaddr_un;
10#endif
11 u_char sockaddr_data[NGX_SOCKADDRLEN];
12 } u;
13
14 socklen_t socklen;
15
16 unsigned set:1;
17 unsigned default_server:1;
18 unsigned bind:1;
19 unsigned wildcard:1;
20#if (NGX_HTTP_SSL)
21 unsigned ssl:1;
22#endif
23#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
24 unsigned ipv6only:2;
25#endif
26
27 int backlog;
28 int rcvbuf;
29 int sndbuf;
30#if (NGX_HAVE_SETFIB)
31 int setfib;
32#endif
33
34#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
35 char *accept_filter;
36#endif
37#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
38 ngx_uint_t deferred_accept;
39#endif
40
41 u_char addr[NGX_SOCKADDR_STRLEN + 1];
42} ngx_http_listen_opt_t;
複製程式碼
上面的幾個資料結構是我們分析listen
指令時常用到的,這裡簡單的說明一下他們的作用。
ngx_url_t
結構體是用來儲存解析address:port
的內容。ngx_http_listen_opt_t
結構體是用來儲存listen
指令後面所配置的選項。為後面建立socket
做準備。
解析地址
我們閱讀ngx_http_core_listen
的原始碼就會發現,解析listen
指令的第一步是呼叫ngx_parse_url()
來解析address:port
部分。這其實是ngx_http_core_listen
函式最重要的一部分,我們先來分析一下這個函式。
首先,我們必須要知道listen
指令配置的address:port
的格式,我們這裡只分析ipv4
格式,我們從nginx
文件中可以查到:
Sets the address and port for IP, or the path for a UNIX-domain socket on which the server will accept requests. Both address and port, or only address or only port can be specified. An address may also be a hostname, for example:
listen 127.0.0.1:8000;
listen 127.0.0.1;
listen 8000;
listen *:8000;
listen localhost:8000;If only address is given, the port 80 is used.
If the directive is not present then either *:80 is used if nginx runs with the superuser privileges, or *:8000 otherwise.
通過上面的文件我們可以知道,address:port
可以有一下幾種格式:
1listen 8080
2listen *:8080
3listen 127.0.0.1:8080
4listen localhost:8080
5listen 127.0.0.1;
複製程式碼
如果我們沒有指定port
,那麼預設是80
埠。
如果我們在server
中沒有配置listen
指令,那麼會有兩種情況:
- 當前啟動
nginx
的是普通使用者,那麼預設為*:80
- 當前啟動
nginx
的是root
使用者,那麼預設為*:8000
那麼nginx
是如何解析address:port
的呢?這就是下面的ngx_parse_url()
函式的功能了。
1 u.url = value[1];
2 u.listen = 1;
3 u.default_port = 80;
4
5 if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
6 if (u.err) {
7 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
8 "%s in \"%V\" of the \"listen\" directive",
9 u.err, &u.url);
10 }
11 return NGX_CONF_ERROR;
12 }
複製程式碼
在呼叫ngx_parse_url()
函式之前,會先進行一些簡單的賦值,
1 // address:port部分
2 u.url = value[1];
3 // 表示當前server顯式的設定了listen指令
4 u.listen = 1;
5 // 設定一個預設的埠號
6 u.default_port = 80;
複製程式碼
由於我們使用的是ipv4
地址,所以最終呼叫的是ngx_parse_inet_url()
,如下:
1static ngx_int_t
2ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
3{
4 u_char *p, *host, *port, *last, *uri, *args;
5 size_t len;
6 ngx_int_t n;
7 struct hostent *h;
8 struct sockaddr_in *sin;
9
10 u->socklen = sizeof(struct sockaddr_in);
11 sin = (struct sockaddr_in *) &u->sockaddr;
12 sin->sin_family = AF_INET;
13 u->family = AF_INET;
14 host = u->url.data;
15 last = host + u->url.len;
16
17 port = ngx_strlchr(host, last, ':');// 判斷是否有埠號
18 uri = ngx_strlchr(host, last, '/');// 判斷是否有path
19 args = ngx_strlchr(host, last, '?');// 判斷是否有QueryString
20
21 if (args) {
22 /*我們的listen指令後面的address:port沒有args,不會執行這裡*/
23 }
24 if (uri) {
25 /*我們的listen指令後面的address:port沒有uri,不會執行這裡*/
26 }
27 if (port) {//如果有埠號
28 port++;//port向後移動一位,表示從此位置開始是埠號
29 len = last - port;//埠號的長度
30 n = ngx_atoi(port, len);// 把埠號轉換為數字
31 u->port = (in_port_t) n;
32 sin->sin_port = htons((in_port_t) n);
33 u->port_text.len = len;
34 u->port_text.data = port;
35 last = port - 1;// 此時last指向了address的最後
36 } else {
37 // 如果我們沒有冒號,這時候有兩種情況,
38// ① 我們沒有指定埠號,如 listen 127.0.0.1
39// ② 我們指定了埠號,但是沒有指定address,如 listen 8080
40 if (uri == NULL) {
41 // 我們在server中顯式的使用了listen指令
42 if (u->listen) {
43 /* test value as port only */
44 // 這句話註釋的很明顯,nginx首先將它作為一個port
45 // 進行轉換,如果成功,那麼就認為這是一個port
46 n = ngx_atoi(host, last - host);
47 if (n != NGX_ERROR) {
48//對於上面的第①種情況,由於無法將 127.0.0.1
49// 轉換為一個正確的埠號,
50// 所以就不會執行下面的if語句,而是執行
51// u->noport = 1 , 表示我們沒有指定埠號
52 u->port = (in_port_t) n;
53 sin->sin_port = htons((in_port_t) n);
54 u->port_text.len = last - host;
55 u->port_text.data = host;
56 u->wildcard = 1;
57 return NGX_OK;
58 }
59 }
60 }
61 // 對於上述的第①種情況,會執行到這裡,表示我們沒有指定埠號
62 u->no_port = 1;
63 }
64// 如果執行到這裡,說明listen後面沒有埠號,只有address
65// len 表示address的長度,
66// 比如 127.0.0.1 或者 localhost的長度
67 len = last - host;
68 if (len == 0) {
69 u->err = "no host";
70 return NGX_ERROR;
71 }
72 if (len == 1 && *host == '*') {
73 len = 0;
74 }
75
76 u->host.len = len;
77 u->host.data = host;
78 if (u->no_resolve) {
79 return NGX_OK;
80 }
81
82 if (len) {
83 // 對於 listen * 的情況,上面的程式碼會把len設定為0,所以不會執行這裡
84// 這裡會首先嚐試把address轉換為ip形式,如果轉換不成功,
85// 那麼就會呼叫gethostbyname()進行DNS地址解析
86// 比如 127.0.0.1這種形式就可以通過 ngx_inet_addr()進行轉換,
87// 這時就不會呼叫gethostbyname()進行DNS解析
88// 但是對於 localhost 這種情況,只能進行DNS地址解析
89 sin->sin_addr.s_addr = ngx_inet_addr(host, len);
90
91 if (sin->sin_addr.s_addr == INADDR_NONE) {
92 p = ngx_alloc(++len, pool->log);
93 (void) ngx_cpystrn(p, host, len);
94
95 h = gethostbyname((const char *) p);
96
97 ngx_free(p);
98
99 if (h == NULL || h->h_addr_list[0] == NULL) {
100 u->err = "host not found";
101 return NGX_ERROR;
102 }
103
104 sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[0]);
105 }
106
107 if (sin->sin_addr.s_addr == INADDR_ANY) {
108 u->wildcard = 1;
109 }
110
111 } else {// address和port都忽略的時候
112 sin->sin_addr.s_addr = INADDR_ANY;
113 u->wildcard = 1;
114 }
115
116 if (u->no_port) {
117 // 如果沒有指定埠號,那麼會使用預設的80埠
118// 從這裡也可以看出來,ngx_url_t 的 default_port 欄位就是用來儲存預設埠的
119// 如果我們沒有指定一個明確的埠號,那麼就會使用這個預設的埠,預設是 80
120 u->port = u->default_port;
121 sin->sin_port = htons(u->default_port);
122 }
123
124 if (u->listen) {
125 return NGX_OK;
126 }
127//因為我們的先決條件是 u->listen = 1,所以下面的語句不會被執行
128 if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
129 return NGX_ERROR;
130 }
131
132 return NGX_OK;
133}
複製程式碼
結合圖片以及程式碼中的註釋,我們簡單的分析一下nginx對listen指令中 address:port
的解析方法:address:port
的格式組合格式有好幾種address
: 可以沒有該欄位,可以為IP地址,可以為域名port
: 可以不設定該欄位,可以為一個固定的埠
所以組合形式就有 3 * 2 = 6
中。nginx
對於他們的解析有固定的格式,如下:
如果address
沒有設定,那麼 u->wildcard = 1
,表示這時一個通配的地址匹配
如果address
設定為一個ip格式,那麼監聽的地址就是這個ip地址
如果address
是一個域名格式,那麼就會對該域名進行DNS地址解析,獲取監聽的IP地址
如果埠號為空,那麼就會使用預設的80埠。
address:port
解析完之後,我們可以從 listen
指令的處理函式 ngx_http_core_listen()
中看到,只有 u.sockaddr
, u.socklen
,以及 u.wildcard
三個欄位被ngx_http_listen_opt_t
結構體用到了,ngx_url_t
的其他欄位都是作為 ngx_parse_url()
函式的輔助欄位使用。我們在後續的分析過程中,可以結合上面的圖片進行學習
內部佈局總結
根據上面的分析,我們對常見的幾種address:port
格式的記憶體佈局進行了總結,如下:
1listen 8080
2listen *:8080
3listen 127.0.0.1:8080
4listen localhost:8080
複製程式碼
對應的圖片如下:
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達