listen原始碼分析第一篇 address:port分析
微信公眾號:關注可瞭解更多的 Nginx
知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!
前言
本篇文章詳細介紹一下 listen
指令的解析,以及 socket
的建立過程。
listen
的內容太多了,並且牽涉到後面的很多地方,所以只能再一次的回到這裡仔細的學習 listen
指令的解析過程。首先要參考 listen
的 nginx
官方文件 ,知道listen的用法,然後才能學習原始碼。
listen配置
我們先從原始檔中找到 listen
的配置項,如下:
1{ 2ngx_string("listen"), 3NGX_HTTP_SRV_CONF|NGX_CONF_1MORE, 4ngx_http_core_listen, 5NGX_HTTP_SRV_CONF_OFFSET, 60, 7NULL 8} 複製程式碼
我們從配置檔案中可以看到, listen
指令的解析函式為 ngx_http_core_listen
,我們下面分析一下這個函式。
使用到的結構體

1typedef struct { 2union { 3struct sockaddrsockaddr; 4struct sockaddr_insockaddr_in; 5#if (NGX_HAVE_INET6) 6struct sockaddr_in6sockaddr_in6; 7#endif 8#if (NGX_HAVE_UNIX_DOMAIN) 9struct sockaddr_unsockaddr_un; 10#endif 11u_charsockaddr_data[NGX_SOCKADDRLEN]; 12} u; 13 14socklen_tsocklen; 15 16unsignedset:1; 17unsigneddefault_server:1; 18unsignedbind:1; 19unsignedwildcard:1; 20#if (NGX_HTTP_SSL) 21unsignedssl:1; 22#endif 23#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) 24unsignedipv6only:2; 25#endif 26 27intbacklog; 28intrcvbuf; 29intsndbuf; 30#if (NGX_HAVE_SETFIB) 31intsetfib; 32#endif 33 34#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) 35char*accept_filter; 36#endif 37#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) 38ngx_uint_tdeferred_accept; 39#endif 40 41u_charaddr[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
可以有一下幾種格式:
1listen8080 2listen*:8080 3listen127.0.0.1:8080 4listenlocalhost:8080 5listen127.0.0.1; 複製程式碼
如果我們沒有指定 port
,那麼預設是 80
埠。
如果我們在 server
中沒有配置 listen
指令,那麼會有兩種情況:
- 當前啟動
nginx
的是普通使用者,那麼預設為*:80
- 當前啟動
nginx
的是root
使用者,那麼預設為*:8000
那麼 nginx
是如何解析 address:port
的呢?這就是下面的 ngx_parse_url()
函式的功能了。
1u.url = value[1]; 2u.listen = 1; 3u.default_port = 80; 4 5if (ngx_parse_url(cf->pool, &u) != NGX_OK) { 6if (u.err) { 7ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 8"%s in \"%V\" of the \"listen\" directive", 9u.err, &u.url); 10} 11return NGX_CONF_ERROR; 12} 複製程式碼
在呼叫 ngx_parse_url()
函式之前,會先進行一些簡單的賦值,
1// address:port部分 2u.url = value[1]; 3// 表示當前server顯式的設定了listen指令 4u.listen = 1; 5// 設定一個預設的埠號 6u.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{ 4u_char*p, *host, *port, *last, *uri, *args; 5size_tlen; 6ngx_int_tn; 7struct hostent*h; 8struct sockaddr_in*sin; 9 10u->socklen = sizeof(struct sockaddr_in); 11sin = (struct sockaddr_in *) &u->sockaddr; 12sin->sin_family = AF_INET; 13u->family = AF_INET; 14host = u->url.data; 15last = host + u->url.len; 16 17port = ngx_strlchr(host, last, ':');// 判斷是否有埠號 18uri = ngx_strlchr(host, last, '/');// 判斷是否有path 19args = ngx_strlchr(host, last, '?');// 判斷是否有QueryString 20 21if (args) { 22/*我們的listen指令後面的address:port沒有args,不會執行這裡*/ 23} 24if (uri) { 25/*我們的listen指令後面的address:port沒有uri,不會執行這裡*/ 26} 27if (port) {//如果有埠號 28port++;//port向後移動一位,表示從此位置開始是埠號 29len = last - port;//埠號的長度 30n = ngx_atoi(port, len);// 把埠號轉換為數字 31u->port = (in_port_t) n; 32sin->sin_port = htons((in_port_t) n); 33u->port_text.len = len; 34u->port_text.data = port; 35last = port - 1;// 此時last指向了address的最後 36} else { 37// 如果我們沒有冒號,這時候有兩種情況, 38// ① 我們沒有指定埠號,如 listen 127.0.0.1 39// ② 我們指定了埠號,但是沒有指定address,如 listen8080 40if (uri == NULL) { 41// 我們在server中顯式的使用了listen指令 42if (u->listen) { 43/* test value as port only */ 44// 這句話註釋的很明顯,nginx首先將它作為一個port 45// 進行轉換,如果成功,那麼就認為這是一個port 46n = ngx_atoi(host, last - host); 47if (n != NGX_ERROR) { 48//對於上面的第①種情況,由於無法將 127.0.0.1 49// 轉換為一個正確的埠號, 50// 所以就不會執行下面的if語句,而是執行 51// u->noport = 1 , 表示我們沒有指定埠號 52u->port = (in_port_t) n; 53sin->sin_port = htons((in_port_t) n); 54u->port_text.len = last - host; 55u->port_text.data = host; 56u->wildcard = 1; 57return NGX_OK; 58} 59} 60} 61// 對於上述的第①種情況,會執行到這裡,表示我們沒有指定埠號 62u->no_port = 1; 63} 64// 如果執行到這裡,說明listen後面沒有埠號,只有address 65// len 表示address的長度, 66// 比如 127.0.0.1 或者localhost的長度 67len = last - host; 68if (len == 0) { 69u->err = "no host"; 70return NGX_ERROR; 71} 72if (len == 1 && *host == '*') { 73len = 0; 74} 75 76u->host.len = len; 77u->host.data = host; 78if (u->no_resolve) { 79return NGX_OK; 80} 81 82if (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地址解析 89sin->sin_addr.s_addr = ngx_inet_addr(host, len); 90 91if (sin->sin_addr.s_addr == INADDR_NONE) { 92p = ngx_alloc(++len, pool->log); 93(void) ngx_cpystrn(p, host, len); 94 95h = gethostbyname((const char *) p); 96 97ngx_free(p); 98 99if (h == NULL || h->h_addr_list[0] == NULL) { 100u->err = "host not found"; 101return NGX_ERROR; 102} 103 104sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[0]); 105} 106 107if (sin->sin_addr.s_addr == INADDR_ANY) { 108u->wildcard = 1; 109} 110 111} else {// address和port都忽略的時候 112sin->sin_addr.s_addr = INADDR_ANY; 113u->wildcard = 1; 114} 115 116if (u->no_port) { 117// 如果沒有指定埠號,那麼會使用預設的80埠 118// 從這裡也可以看出來,ngx_url_t 的 default_port 欄位就是用來儲存預設埠的 119// 如果我們沒有指定一個明確的埠號,那麼就會使用這個預設的埠,預設是 80 120u->port = u->default_port; 121sin->sin_port = htons(u->default_port); 122} 123 124if (u->listen) { 125return NGX_OK; 126} 127//因為我們的先決條件是 u->listen = 1,所以下面的語句不會被執行 128if (ngx_inet_resolve_host(pool, u) != NGX_OK) { 129return NGX_ERROR; 130} 131 132return 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
格式的記憶體佈局進行了總結,如下:
1listen8080 2listen*:8080 3listen127.0.0.1:8080 4listenlocalhost:8080 複製程式碼
對應的圖片如下:




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