服务器之家:专注于服务器技术及软件下载分享
分类导航

云服务器|WEB服务器|FTP服务器|邮件服务器|虚拟主机|服务器安全|DNS服务器|服务器知识|Nginx|IIS|Tomcat|

服务器之家 - 服务器技术 - Nginx - nginx中的listen指令实例解析

nginx中的listen指令实例解析

2019-12-31 14:56郑尔多斯 Nginx

这篇文章主要给大家介绍了关于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

延伸 · 阅读

精彩推荐
  • Nginx通过Nginx规则重写URL去掉index.php不显示index.php

    通过Nginx规则重写URL去掉index.php不显示index.php

    Nginx不仅占用内存少,并发能力强,而且拓展功能丰富,可以通过安装模板来强化功能,也能通过规则优化,优化服务器并发处理能力,是建站的不二之选...

    Genius日记5872020-10-16
  • NginxNginx location 和 proxy_pass路径配置问题小结

    Nginx location 和 proxy_pass路径配置问题小结

    本文是基于 location 的匹配末尾是否配置 / 和 proxy_pass 末尾是否配置 / ,进行测试,完全还原了整个测试过程,本文给大家介绍Nginx location 基本配置及相关配...

    自由早晚乱余生18742021-09-24
  • Nginxnginx ssl免密码重启教程详解

    nginx ssl免密码重启教程详解

    这篇文章给大家介绍了nginx 如何启动以及nginx ssl 免密码重启 的方法,非常不错,具有参考借鉴价值,需要的朋友参考下吧 ...

    mrr4272019-11-19
  • Nginx利用nginx和腾讯云免费证书制作https的方法

    利用nginx和腾讯云免费证书制作https的方法

    这篇文章主要介绍了利用nginx和腾讯云免费证书制作https的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    dalaoyang5992019-12-30
  • Nginxnginx rewrite 伪静态配置参数和使用例子

    nginx rewrite 伪静态配置参数和使用例子

    nginx下伪静态配置参数详细说明,使用nginx的朋友,nginx rewrite 伪静态配置参数和使用例子 附正则使用说明 ...

    服务器之家3102019-10-08
  • NginxNginx动静分离实现案例代码解析

    Nginx动静分离实现案例代码解析

    这篇文章主要介绍了Nginx动静分离实现案例代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参...

    盗哥泡茶去了3382020-09-27
  • Nginx如何优化Nginx的处理性能

    如何优化Nginx的处理性能

    Nginx是一个很强大的高性能Web和反向代理服务,它具有很多非常优越的特性,在连接高并发的情况下,Nginx是Apache服务不错的替代品。其特点是占有内存少,...

    Dockone.io5142020-12-11
  • NginxNginx Rewrite使用场景及代码案例详解

    Nginx Rewrite使用场景及代码案例详解

    这篇文章主要介绍了Nginx Rewrite使用场景及代码案例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可...

    盗哥泡茶去了11862020-09-27