listen源碼分析第一篇 address:port分析

微信公衆號:鄭爾多斯
關注可瞭解更多的Nginx知識。任何問題或建議,請公衆號留言;
關注公衆號,有趣有內涵的文章第一時間送達!css

前言

本篇文章詳細介紹一下listen指令的解析,以及socket的建立過程。
listen的內容太多了,而且牽涉到後面的不少地方,因此只能再一次的回到這裏仔細的學習listen指令的解析過程。首先要參考listennginx官方文檔,知道listen的用法,而後才能學習源碼。html

listen配置

咱們先從源文件中找到listen的配置項,以下:nginx

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,咱們下面分析一下這個函數。微信

使用到的結構體

ngx_url_t
ngx_url_t
 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];
42ngx_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文檔中能夠查到:app

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:dom

listen 127.0.0.1:8000;
listen 127.0.0.1;
listen 8000;
listen *:8000;
listen localhost:8000;socket

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() 函數的輔助字段使用。咱們在後續的分析過程當中,能夠結合上面的圖片進行學習

ngx_url_t結構體使用狀況
ngx_url_t結構體使用狀況

內部佈局總結

根據上面的分析,咱們對常見的幾種address:port格式的內存佈局進行了總結,以下:

1listen   8080
2listen   *:8080
3listen    127.0.0.1:8080
4listen     localhost:8080
複製代碼

對應的圖片以下:

圖1
圖1
圖2
圖2
圖3
圖3
圖4
圖4

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

鄭爾多斯
鄭爾多斯
相關文章
相關標籤/搜索