小知识:nginx中的listen指令实例解析

剧情回顾

上一篇文章我们分析了location指令的解析过程,简单的回顾一下这个内容:每个location对应一个ngx_http_core_loc_conf_t结构体,所有的location通过一个双向队列连接在一起。数据结构比较复杂。

listen指令

nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解。与网络有关的配置命令主要有两个:listen和sever_name。listen命令设置nginx监听地址,对于IP协议,这个地址就是address和port,对于UNIX域套接字协议,这个地址就是path,一条listen指令只能指定一个address或者port,address也可以是主机名

从这一篇文章开始,我们分析listen指令的解析过程,listen指令的配置如下:从nginx.org的手册中我们可以获取listen的使用方法:

?
1
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指令携带的参数是很复杂的。不过,我们一般很少关注那些不太常用的参数,以下是一些常用的配置方式:

?
1
2
3
4
5
listen 127.0.0.1:8000;
listen 127.0.0.1 不加端口,默认监听80端口;
listen 8000
listen *:8000
listen localhost:8000

解析listen指令中的uri和端口

从上面的内容知道,listen有多种用法,我们在解析的时候需要获取到listen指令的端口号和uri部分,nginx提供了ngx_parse_url()方法来解析uri和port,该函数在解析listen指令的时候会被调用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的协议
if (len >= 5 && ngx_strncasecmp(p, (u_char *) “unix:”, 5) == 0) {
return ngx_parse_unix_domain_url(pool, u);
}
// 解析IPV6协议
if (len && p[0] == [) {
return ngx_parse_inet6_url(pool, u);
}
// 解析IPV4协议
return ngx_parse_inet_url(pool, u);
}

我们使用的是IPV4协议,这里分析ngx_parse_inet_url()函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// 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, ?); // 找到参数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;
}

这个函数就是解析了我们listen的地址和端口号,我们的配置文件中,端口号为80,并没有配置监听地址,所以u->wildcard = 1,表示这是一个通配符,要监听该服务器所有ip地址的这个端口号。

解析listen指令

下面从源码中看一下listen的配置:

?
1
2
3
4
5
6
7
8
{
ngx_string(“listen”),
NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_listen,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL
}

从配置文件中我们可以知道,listen只能出现在server 模块中,可以带有多个参数。

对应的处理函数为 ngx_http_core_listen,下面我们分析这个函数,我们删除了一些进行错误判断的代码,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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的各种参数,对我们这里的分析没有用处
}
if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
return NGX_CONF_OK;
}
return NGX_CONF_ERROR;
}

这个函数的整体流程就是解析listen指令的各个参数,生成一个 ngx_http_listen_opt_t,顾名思义,这个结构体就是保存一些监听端口的选项(listening port option)。这里调用了一个函数ngx_parse_url(),我们上面已经分析过了,这个函数的作用就是解析url中的address和port。

然后最重要的部分就要到了,ngx_http_core_listen()函数在最后面调用了ngx_http_add_listen()函数,该函数是将listen的端口信息保存到ngx_http_core_main_conf_t结构体的ports动态数组中。

ngx_http_add_listen()函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// cf: 配置结构体
// cscf: listen指令所在的server的配置结构体
// 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结构体ngx_http_core_main_conf_t
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
// ports字段是一个数组
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);
}

这个函数将端口号的信息保存到了 ngx_http_core_main_conf_t结构体的port字段中。

%小知识:nginx中的listen指令实例解析-猿站网-插图

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://juejin.im/post/5c03ec066fb9a049b7801ca1

声明: 猿站网有关资源均来自网络搜集与网友提供,任何涉及商业盈利目的的均不得使用,否则产生的一切后果将由您自己承担! 本平台资源仅供个人学习交流、测试使用 所有内容请在下载后24小时内删除,制止非法恶意传播,不对任何下载或转载者造成的危害负任何法律责任!也请大家支持、购置正版! 。本站一律禁止以任何方式发布或转载任何违法的相关信息访客发现请向站长举报,会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。本网站的资源部分来源于网络,如有侵权烦请发送邮件至:2697268773@qq.com进行处理。
建站知识

小知识:从 Linux 终端打印文件

2023-4-11 4:08:19

建站知识

小知识:nginx配置ssl实现https访问的步骤(适合新手)

2023-4-11 4:19:50

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索