劇情回顧
上一篇文章我們分析了location指令的解析過程,簡單的回顧一下這個內(nèi)容:每個location對應(yīng)一個ngx_http_core_loc_conf_t結(jié)構(gòu)體,所有的location通過一個雙向隊列連接在一起。數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜。
listen指令
nginx作為一個高性能的HTTP服務(wù)器,網(wǎng)絡(luò)的處理是其核心,了解網(wǎng)絡(luò)的初始化有助于加深對nginx網(wǎng)絡(luò)處理的了解。與網(wǎng)絡(luò)有關(guān)的配置命令主要有兩個:listen和sever_name。listen命令設(shè)置nginx監(jiān)聽地址,對于IP協(xié)議,這個地址就是address和port,對于UNIX域套接字協(xié)議,這個地址就是path,一條listen指令只能指定一個address或者port,address也可以是主機名
從這一篇文章開始,我們分析listen指令的解析過程,listen指令的配置如下:從nginx.org的手冊中我們可以獲取listen的使用方法:
listen address[:port] [default_server] [setfib=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [ssl] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
一個listen指令攜帶的參數(shù)是很復(fù)雜的。不過,我們一般很少關(guān)注那些不太常用的參數(shù),以下是一些常用的配置方式:
listen 127.0.0.1:8000;
listen 127.0.0.1 不加端口,默認監(jiān)聽80端口;
listen 8000
listen *:8000
listen localhost:8000
解析listen指令中的uri和端口
從上面的內(nèi)容知道,listen有多種用法,我們在解析的時候需要獲取到listen指令的端口號和uri部分,nginx提供了ngx_parse_url()方法來解析uri和port,該函數(shù)在解析listen指令的時候會被調(diào)用。
ngx_int_t
ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{
u_char *p;
size_t len;
p = u->url.data;
len = u->url.len;
// 這里是解析unix domain的協(xié)議
if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
return ngx_parse_unix_domain_url(pool, u);
}
// 解析IPV6協(xié)議
if (len && p[0] == '[') {
return ngx_parse_inet6_url(pool, u);
}
// 解析IPV4協(xié)議
return ngx_parse_inet_url(pool, u);
}
我們使用的是IPV4協(xié)議,這里分析ngx_parse_inet_url()函數(shù)
// u.url = "80";
// u.listen = 1;
// u.default_port = 80;
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 sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
u->socklen = sizeof(struct sockaddr_in);
sin = (struct sockaddr_in *) &u->sockaddr;
sin->sin_family = AF_INET;// IPV4類型
u->family = AF_INET;
host = u->url.data; // "80"
last = host + u->url.len; // host的最后字符的位置
port = ngx_strlchr(host, last, ':'); // 找到port, 這里為 NULL
uri = ngx_strlchr(host, last, '/'); // 找到uri,這里為 NULL
args = ngx_strlchr(host, last, '?'); // 找到參數(shù)args,這里為 NULL
if (args) {
if (uri == NULL || args < uri) {
uri = args;
}
}
if (uri) {
if (u->listen || !u->uri_part) {
u->err = "invalid host";
return NGX_ERROR;
}
u->uri.len = last - uri;
u->uri.data = uri;
last = uri;
if (uri < port) {
port = NULL;
}
}
if (port) {
port++;
len = last - port;
n = ngx_atoi(port, len);
if (n < 1 || n > 65535) {
u->err = "invalid port";
return NGX_ERROR;
}
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;
} else {
if (uri == NULL) {
if (u->listen) {
/* test value as port only */
n = ngx_atoi(host, last - host);
if (n != NGX_ERROR) {
if (n < 1 || n > 65535) {
u->err = "invalid port";
return NGX_ERROR;
}
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;
}
}
}
u->no_port = 1;
u->port = u->default_port;
sin->sin_port = htons(u->default_port);
}
len = last - host;
if (len == 0) {
u->err = "no host";
return NGX_ERROR;
}
u->host.len = len;
u->host.data = host;
if (u->listen && len == 1 && *host == '*') {
sin->sin_addr.s_addr = INADDR_ANY;
u->wildcard = 1;
return NGX_OK;
}
sin->sin_addr.s_addr = ngx_inet_addr(host, len);
if (sin->sin_addr.s_addr != INADDR_NONE) {
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
u->naddrs = 1;
u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
if (u->addrs == NULL) {
return NGX_ERROR;
}
sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
if (sin == NULL) {
return NGX_ERROR;
}
ngx_memcpy(sin, &u->sockaddr, sizeof(struct sockaddr_in));
u->addrs[0].sockaddr = (struct sockaddr *) sin;
u->addrs[0].socklen = sizeof(struct sockaddr_in);
p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
if (p == NULL) {
return NGX_ERROR;
}
u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
&u->host, u->port) - p;
u->addrs[0].name.data = p;
return NGX_OK;
}
if (u->no_resolve) {
return NGX_OK;
}
if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
return NGX_ERROR;
}
u->family = u->addrs[0].sockaddr->sa_family;
u->socklen = u->addrs[0].socklen;
ngx_memcpy(&u->sockaddr, u->addrs[0].sockaddr, u->addrs[0].socklen);
switch (u->family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) &u->sockaddr;
if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
u->wildcard = 1;
}
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) &u->sockaddr;
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
break;
}
return NGX_OK;
}
這個函數(shù)就是解析了我們listen的地址和端口號,我們的配置文件中,端口號為80,并沒有配置監(jiān)聽地址,所以u->wildcard = 1,表示這是一個通配符,要監(jiān)聽該服務(wù)器所有ip地址的這個端口號。
解析listen指令
下面從源碼中看一下listen的配置:
{
ngx_string("listen"),
NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_listen,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL
}
從配置文件中我們可以知道,listen只能出現(xiàn)在server 模塊中,可以帶有多個參數(shù)。
對應(yīng)的處理函數(shù)為 ngx_http_core_listen,下面我們分析這個函數(shù),我們刪除了一些進行錯誤判斷的代碼,
static char *
ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_srv_conf_t *cscf = conf;
ngx_str_t *value, size;
ngx_url_t u;
ngx_uint_t n;
ngx_http_listen_opt_t lsopt;
cscf->listen = 1;
value = cf->args->elts;
ngx_memzero(&u, sizeof(ngx_url_t));
u.url = value[1];
u.listen = 1;
u.default_port = 80;
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
return NGX_CONF_ERROR;
}
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
ngx_memcpy(&lsopt.sockaddr.sockaddr, &u.sockaddr, u.socklen);
lsopt.socklen = u.socklen;
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
lsopt.setfib = -1;
#endif
#if (NGX_HAVE_TCP_FASTOPEN)
lsopt.fastopen = -1;
#endif
lsopt.wildcard = u.wildcard;
#if (NGX_HAVE_INET6)
lsopt.ipv6only = 1;
#endif
(void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen, lsopt.addr,
NGX_SOCKADDR_STRLEN, 1);
for (n = 2; n < cf->args->nelts; n++) {
if (ngx_strcmp(value[n].data, "default_server") == 0
|| ngx_strcmp(value[n].data, "default") == 0)
{
lsopt.default_server = 1;
continue;
}
// 這里面的其他代碼都是處理listen的各種參數(shù),對我們這里的分析沒有用處
}
if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
return NGX_CONF_OK;
}
return NGX_CONF_ERROR;
}
這個函數(shù)的整體流程就是解析listen指令的各個參數(shù),生成一個 ngx_http_listen_opt_t,顧名思義,這個結(jié)構(gòu)體就是保存一些監(jiān)聽端口的選項(listening port option)。這里調(diào)用了一個函數(shù)ngx_parse_url(),我們上面已經(jīng)分析過了,這個函數(shù)的作用就是解析url中的address和port。
然后最重要的部分就要到了,ngx_http_core_listen()函數(shù)在最后面調(diào)用了ngx_http_add_listen()函數(shù),該函數(shù)是將listen的端口信息保存到ngx_http_core_main_conf_t結(jié)構(gòu)體的ports動態(tài)數(shù)組中。
ngx_http_add_listen()函數(shù)
// cf: 配置結(jié)構(gòu)體
// cscf: listen指令所在的server的配置結(jié)構(gòu)體
// lsopt : ngx_http_core_listen()生成的listen option
ngx_int_t
ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_listen_opt_t *lsopt)
{
in_port_t p;
ngx_uint_t i;
struct sockaddr *sa;
ngx_http_conf_port_t *port;
ngx_http_core_main_conf_t *cmcf;
// 獲取 ngx_http_core_module模塊的main_conf結(jié)構(gòu)體ngx_http_core_main_conf_t
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
// ports字段是一個數(shù)組
if (cmcf->ports == NULL) {
cmcf->ports = ngx_array_create(cf->temp_pool, 2,
sizeof(ngx_http_conf_port_t));
if (cmcf->ports == NULL) {
return NGX_ERROR;
}
}
sa = &lsopt->sockaddr.sockaddr;
p = ngx_inet_get_port(sa);
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
if (p != port[i].port || sa->sa_family != port[i].family) {
continue;
}
/* a port is already in the port list */
return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
}
/* add a port to the port list */
port = ngx_array_push(cmcf->ports);
if (port == NULL) {
return NGX_ERROR;
}
port->family = sa->sa_family;
port->port = p;
port->addrs.elts = NULL;
return ngx_http_add_address(cf, cscf, port, lsopt);
}
這個函數(shù)將端口號的信息保存到了 ngx_http_core_main_conf_t結(jié)構(gòu)體的port字段中。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。