微信公眾號:鄭爾多斯
關注可了解更多的Nginx
知識矾睦。任何問題或建議勾栗,請公眾號留言;
關注公眾號苍苞,有趣有內涵的文章第一時間送達狰晚!
前言
本篇文章詳細介紹一下listen
指令的解析筒饰,以及socket
的創(chuàng)建過程。
listen
的內容太多了壁晒,并且牽涉到后面的很多地方瓷们,所以只能再一次的回到這里仔細的學習listen
指令的解析過程。首先要參考listen
的nginx
官方文檔秒咐,知道listen的用法谬晕,然后才能學習源碼。
listen配置
我們先從源文件中找到listen
的配置項携取,如下:
{
ngx_string("listen"),
NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_listen,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL
}
我們從配置文件中可以看到攒钳,listen
指令的解析函數為ngx_http_core_listen
,我們下面分析一下這個函數雷滋。
使用到的結構體
typedef struct {
union {
struct sockaddr sockaddr;
struct sockaddr_in sockaddr_in;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 sockaddr_in6;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
struct sockaddr_un sockaddr_un;
#endif
u_char sockaddr_data[NGX_SOCKADDRLEN];
} u;
socklen_t socklen;
unsigned set:1;
unsigned default_server:1;
unsigned bind:1;
unsigned wildcard:1;
#if (NGX_HTTP_SSL)
unsigned ssl:1;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:2;
#endif
int backlog;
int rcvbuf;
int sndbuf;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
char *accept_filter;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
ngx_uint_t deferred_accept;
#endif
u_char addr[NGX_SOCKADDR_STRLEN + 1];
} ngx_http_listen_opt_t;
上面的幾個數據結構是我們分析listen
指令時常用到的不撑,這里簡單的說明一下他們的作用。
-
ngx_url_t
結構體是用來保存解析address:port
的內容惊豺。 -
ngx_http_listen_opt_t
結構體是用來保存listen
指令后面所配置的選項燎孟。為后面創(chuàng)建socket
做準備禽作。
解析地址
我們閱讀ngx_http_core_listen
的源碼就會發(fā)現尸昧,解析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
可以有一下幾種格式:
listen 8080
listen *:8080
listen 127.0.0.1:8080
listen localhost:8080
listen 127.0.0.1;
如果我們沒有指定port
,那么默認是80
端口蕉鸳。
如果我們在server
中沒有配置listen
指令乎赴,那么會有兩種情況:
- 當前啟動
nginx
的是普通用戶,那么默認為*:80
- 當前啟動
nginx
的是root
用戶潮尝,那么默認為*:8000
那么nginx
是如何解析address:port
的呢榕吼?這就是下面的ngx_parse_url()
函數的功能了。
u.url = value[1];
u.listen = 1;
u.default_port = 80;
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
if (u.err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in \"%V\" of the \"listen\" directive",
u.err, &u.url);
}
return NGX_CONF_ERROR;
}
在調用ngx_parse_url()
函數之前勉失,會先進行一些簡單的賦值羹蚣,
// address:port部分
u.url = value[1];
// 表示當前server顯式的設置了listen指令
u.listen = 1;
// 設置一個默認的端口號
u.default_port = 80;
由于我們使用的是ipv4
地址,所以最終調用的是ngx_parse_inet_url()
乱凿,如下:
static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
u_char *p, *host, *port, *last, *uri, *args;
size_t len;
ngx_int_t n;
struct hostent *h;
struct sockaddr_in *sin;
u->socklen = sizeof(struct sockaddr_in);
sin = (struct sockaddr_in *) &u->sockaddr;
sin->sin_family = AF_INET;
u->family = AF_INET;
host = u->url.data;
last = host + u->url.len;
port = ngx_strlchr(host, last, ':');// 判斷是否有端口號
uri = ngx_strlchr(host, last, '/');// 判斷是否有path
args = ngx_strlchr(host, last, '?');// 判斷是否有QueryString
if (args) {
/*我們的listen指令后面的address:port沒有args顽素,不會執(zhí)行這里*/
}
if (uri) {
/*我們的listen指令后面的address:port沒有uri,不會執(zhí)行這里*/
}
if (port) {//如果有端口號
port++;//port向后移動一位徒蟆,表示從此位置開始是端口號
len = last - port;//端口號的長度
n = ngx_atoi(port, len);// 把端口號轉換為數字
u->port = (in_port_t) n;
sin->sin_port = htons((in_port_t) n);
u->port_text.len = len;
u->port_text.data = port;
last = port - 1;// 此時last指向了address的最后
} else {
// 如果我們沒有冒號,這時候有兩種情況胁出,
// ① 我們沒有指定端口號,如 listen 127.0.0.1
// ② 我們指定了端口號段审,但是沒有指定address划鸽,如 listen 8080
if (uri == NULL) {
// 我們在server中顯式的使用了listen指令
if (u->listen) {
/* test value as port only */
// 這句話注釋的很明顯,nginx首先將它作為一個port
// 進行轉換戚哎,如果成功裸诽,那么就認為這是一個port
n = ngx_atoi(host, last - host);
if (n != NGX_ERROR) {
//對于上面的第①種情況,由于無法將 127.0.0.1
// 轉換為一個正確的端口號型凳,
// 所以就不會執(zhí)行下面的if語句丈冬,而是執(zhí)行
// u->noport = 1 , 表示我們沒有指定端口號
u->port = (in_port_t) n;
sin->sin_port = htons((in_port_t) n);
u->port_text.len = last - host;
u->port_text.data = host;
u->wildcard = 1;
return NGX_OK;
}
}
}
// 對于上述的第①種情況,會執(zhí)行到這里甘畅,表示我們沒有指定端口號
u->no_port = 1;
}
// 如果執(zhí)行到這里埂蕊,說明listen后面沒有端口號,只有address
// len 表示address的長度疏唾,
// 比如 127.0.0.1 或者 localhost的長度
len = last - host;
if (len == 0) {
u->err = "no host";
return NGX_ERROR;
}
if (len == 1 && *host == '*') {
len = 0;
}
u->host.len = len;
u->host.data = host;
if (u->no_resolve) {
return NGX_OK;
}
if (len) {
// 對于 listen * 的情況蓄氧,上面的代碼會把len設置為0,所以不會執(zhí)行這里
// 這里會首先嘗試把address轉換為ip形式槐脏,如果轉換不成功喉童,
// 那么就會調用gethostbyname()進行DNS地址解析
// 比如 127.0.0.1這種形式就可以通過 ngx_inet_addr()進行轉換,
// 這時就不會調用gethostbyname()進行DNS解析
// 但是對于 localhost 這種情況顿天,只能進行DNS地址解析
sin->sin_addr.s_addr = ngx_inet_addr(host, len);
if (sin->sin_addr.s_addr == INADDR_NONE) {
p = ngx_alloc(++len, pool->log);
(void) ngx_cpystrn(p, host, len);
h = gethostbyname((const char *) p);
ngx_free(p);
if (h == NULL || h->h_addr_list[0] == NULL) {
u->err = "host not found";
return NGX_ERROR;
}
sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[0]);
}
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
} else {// address和port都忽略的時候
sin->sin_addr.s_addr = INADDR_ANY;
u->wildcard = 1;
}
if (u->no_port) {
// 如果沒有指定端口號堂氯,那么會使用默認的80端口
// 從這里也可以看出來蔑担,ngx_url_t 的 default_port 字段就是用來保存默認端口的
// 如果我們沒有指定一個明確的端口號,那么就會使用這個默認的端口咽白,默認是 80
u->port = u->default_port;
sin->sin_port = htons(u->default_port);
}
if (u->listen) {
return NGX_OK;
}
//因為我們的先決條件是 u->listen = 1啤握,所以下面的語句不會被執(zhí)行
if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
結合圖片以及代碼中的注釋,我們簡單的分析一下nginx對listen指令中 address:port
的解析方法:
address:port
的格式組合格式有好幾種
address
: 可以沒有該字段晶框,可以為IP地址排抬,可以為域名
port
: 可以不設置該字段,可以為一個固定的端口
所以組合形式就有 3 * 2 = 6
中授段。
nginx
對于他們的解析有固定的格式畜埋,如下:
如果address
沒有設置,那么 u->wildcard = 1
畴蒲,表示這時一個通配的地址匹配
如果address
設置為一個ip格式悠鞍,那么監(jiān)聽的地址就是這個ip地址
如果address
是一個域名格式,那么就會對該域名進行DNS地址解析模燥,獲取監(jiān)聽的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()
函數的輔助字段使用浩嫌。我們在后續(xù)的分析過程中,可以結合上面的圖片進行學習
內部布局總結
根據上面的分析补胚,我們對常見的幾種address:port
格式的內存布局進行了總結码耐,如下:
listen 8080
listen *:8080
listen 127.0.0.1:8080
listen localhost:8080
對應的圖片如下:
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯溶其,更多精彩內容第一時間送達