listen源码分析第一篇 address:port分析

栏目: 服务器 · Nginx · 发布时间: 5年前

内容简介:微信公众号:关注可了解更多的关注公众号,有趣有内涵的文章第一时间送达!本篇文章详细介绍一下

微信公众号:关注可了解更多的 Nginx 知识。任何问题或建议,请公众号留言;

关注公众号,有趣有内涵的文章第一时间送达!

前言

本篇文章详细介绍一下 listen 指令的解析,以及 socket 的创建过程。

listen 的内容太多了,并且牵涉到后面的很多地方,所以只能再一次的回到这里仔细的学习 listen 指令的解析过程。首先要参考 listen nginx 官方文档 ,知道listen的用法,然后才能学习源码。

listen配置

我们先从源文件中找到 listen 的配置项,如下:

1{ 
2      ngx_string("listen"),
3      NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
4      ngx_http_core_listen,
5      NGX_HTTP_SRV_CONF_OFFSET,
6      0,
7      NULL 
8}
复制代码

我们从配置文件中可以看到, listen 指令的解析函数为 ngx_http_core_listen ,我们下面分析一下这个函数。

使用到的结构体

listen源码分析第一篇 address:port分析
ngx_url_t
 1typedef struct {
 2    union {
 3        struct sockaddr        sockaddr;
 4        struct sockaddr_in     sockaddr_in;
 5#if (NGX_HAVE_INET6)
 6        struct sockaddr_in6    sockaddr_in6;
 7#endif
 8#if (NGX_HAVE_UNIX_DOMAIN)
 9        struct sockaddr_un     sockaddr_un;
10#endif
11        u_char                 sockaddr_data[NGX_SOCKADDRLEN];
12    } u;
13
14    socklen_t                  socklen;
15
16    unsigned                   set:1;
17    unsigned                   default_server:1;
18    unsigned                   bind:1;
19    unsigned                   wildcard:1;
20#if (NGX_HTTP_SSL)
21    unsigned                   ssl:1;
22#endif
23#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
24    unsigned                   ipv6only:2;
25#endif
26
27    int                        backlog;
28    int                        rcvbuf;
29    int                        sndbuf;
30#if (NGX_HAVE_SETFIB)
31    int                        setfib;
32#endif
33
34#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
35    char                      *accept_filter;
36#endif
37#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
38    ngx_uint_t                 deferred_accept;
39#endif
40
41    u_char                     addr[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 可以有一下几种格式:

1listen   8080
2listen   *:8080
3listen    127.0.0.1:8080
4listen     localhost:8080
5listen   127.0.0.1;
复制代码

如果我们没有指定 port ,那么默认是 80 端口。

如果我们在 server 中没有配置 listen 指令,那么会有两种情况:

  • 当前启动 nginx 的是普通用户,那么默认为 *:80
  • 当前启动 nginx 的是 root 用户,那么默认为 *:8000

那么 nginx 是如何解析 address:port 的呢?这就是下面的 ngx_parse_url() 函数的功能了。

 1    u.url = value[1];
 2    u.listen = 1;
 3    u.default_port = 80;
 4
 5    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
 6        if (u.err) {
 7            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
 8              "%s in \"%V\" of the \"listen\" directive",
 9                               u.err, &u.url);
10        }
11        return NGX_CONF_ERROR;
12    }
复制代码

在调用 ngx_parse_url() 函数之前,会先进行一些简单的赋值,

1    // address:port部分
2    u.url = value[1];
3    // 表示当前server显式的设置了listen指令
4    u.listen = 1; 
5    // 设置一个默认的端口号
6    u.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{
  4    u_char              *p, *host, *port, *last, *uri, *args;
  5    size_t               len;
  6    ngx_int_t            n;
  7    struct hostent      *h;
  8    struct sockaddr_in  *sin;
  9
 10    u->socklen = sizeof(struct sockaddr_in);
 11    sin = (struct sockaddr_in *) &u->sockaddr;
 12    sin->sin_family = AF_INET;
 13    u->family = AF_INET;
 14    host = u->url.data;
 15    last = host + u->url.len;
 16
 17    port = ngx_strlchr(host, last, ':');// 判断是否有端口号
 18    uri = ngx_strlchr(host, last, '/');// 判断是否有path
 19    args = ngx_strlchr(host, last, '?');// 判断是否有QueryString
 20
 21    if (args) {
 22        /*我们的listen指令后面的address:port没有args,不会执行这里*/
 23    }
 24    if (uri) {
 25        /*我们的listen指令后面的address:port没有uri,不会执行这里*/
 26    }
 27    if (port) {//如果有端口号
 28        port++;//port向后移动一位,表示从此位置开始是端口号
 29        len = last - port;//端口号的长度
 30        n = ngx_atoi(port, len);// 把端口号转换为数字
 31        u->port = (in_port_t) n;
 32        sin->sin_port = htons((in_port_t) n);
 33        u->port_text.len = len;
 34        u->port_text.data = port;
 35        last = port - 1;// 此时last指向了address的最后
 36    } else {
 37    // 如果我们没有冒号,这时候有两种情况,
 38// ① 我们没有指定端口号,如 listen 127.0.0.1
 39// ② 我们指定了端口号,但是没有指定address,如 listen  8080
 40        if (uri == NULL) {
 41        // 我们在server中显式的使用了listen指令
 42            if (u->listen) {
 43                /* test value as port only */
 44                // 这句话注释的很明显,nginx首先将它作为一个port
 45                // 进行转换,如果成功,那么就认为这是一个port
 46                n = ngx_atoi(host, last - host);
 47                if (n != NGX_ERROR) {
 48//对于上面的第①种情况,由于无法将 127.0.0.1 
 49// 转换为一个正确的端口号,
 50// 所以就不会执行下面的if语句,而是执行
 51// u->noport = 1 , 表示我们没有指定端口号
 52                    u->port = (in_port_t) n;
 53                    sin->sin_port = htons((in_port_t) n);
 54                    u->port_text.len = last - host;
 55                    u->port_text.data = host;
 56                    u->wildcard = 1;
 57                    return NGX_OK;
 58                }
 59            }
 60        }  
 61    // 对于上述的第①种情况,会执行到这里,表示我们没有指定端口号
 62        u->no_port = 1;
 63    }
 64// 如果执行到这里,说明listen后面没有端口号,只有address
 65// len 表示address的长度,
 66// 比如 127.0.0.1 或者  localhost的长度
 67    len = last - host;
 68    if (len == 0) {
 69        u->err = "no host";
 70        return NGX_ERROR;
 71    }
 72    if (len == 1 && *host == '*') {
 73        len = 0;
 74    }
 75
 76    u->host.len = len;
 77    u->host.data = host;
 78    if (u->no_resolve) {
 79        return NGX_OK;
 80    }
 81
 82    if (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地址解析
 89        sin->sin_addr.s_addr = ngx_inet_addr(host, len);
 90
 91        if (sin->sin_addr.s_addr == INADDR_NONE) {
 92            p = ngx_alloc(++len, pool->log);
 93            (void) ngx_cpystrn(p, host, len);
 94
 95            h = gethostbyname((const char *) p);
 96
 97            ngx_free(p);
 98
 99            if (h == NULL || h->h_addr_list[0] == NULL) {
100                u->err = "host not found";
101                return NGX_ERROR;
102            }
103
104            sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[0]);
105        }
106
107        if (sin->sin_addr.s_addr == INADDR_ANY) {
108            u->wildcard = 1;
109        }
110
111    } else {// address和port都忽略的时候
112        sin->sin_addr.s_addr = INADDR_ANY;
113        u->wildcard = 1;
114    }
115
116    if (u->no_port) {
117    // 如果没有指定端口号,那么会使用默认的80端口
118// 从这里也可以看出来,ngx_url_t 的 default_port 字段就是用来保存默认端口的
119// 如果我们没有指定一个明确的端口号,那么就会使用这个默认的端口,默认是 80
120        u->port = u->default_port;
121        sin->sin_port = htons(u->default_port);
122    }
123
124    if (u->listen) {
125        return NGX_OK;
126    }
127//因为我们的先决条件是 u->listen = 1,所以下面的语句不会被执行
128    if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
129        return NGX_ERROR;
130    }
131
132    return 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() 函数的辅助字段使用。我们在后续的分析过程中,可以结合上面的图片进行学习

listen源码分析第一篇 address:port分析
ngx_url_t结构体使用情况

内部布局总结

根据上面的分析,我们对常见的几种 address:port 格式的内存布局进行了总结,如下:

1listen   8080
2listen   *:8080
3listen    127.0.0.1:8080
4listen     localhost:8080
复制代码

对应的图片如下:

listen源码分析第一篇 address:port分析
图1
listen源码分析第一篇 address:port分析
图2
listen源码分析第一篇 address:port分析
图3
listen源码分析第一篇 address:port分析
图4

喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达

listen源码分析第一篇 address:port分析
郑尔多斯

以上所述就是小编给大家介绍的《listen源码分析第一篇 address:port分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大学程序设计课程与竞赛训练教材

大学程序设计课程与竞赛训练教材

吴永辉、王建德 / 机械工业出版社 / 2013-6 / 69.00

本书每章为一个主题,实验内容安排紧扣大学算法和数学的教学,用程序设计竞赛中的算法和数学试题作为实验试题,将算法和数学的教学与程序设计竞赛的解题训练结合在一起;在思维方式和解题策略的训练方面,以问题驱动和启发式引导为主要方式,培养读者通过编程解决问题的能力。 本书特点: 书中给出的234道试题全部精选自ACM国际大学生程序设计竞赛的世界总决赛以及各大洲赛区现场赛和网络预赛、大学程序设计竞......一起来看看 《大学程序设计课程与竞赛训练教材》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具