zoukankan      html  css  js  c++  java
  • listen源码分析第一篇 address:port分析

    微信公众号:郑尔多斯
    关注可了解更多的Nginx知识。任何问题或建议,请公众号留言;
    关注公众号,有趣有内涵的文章第一时间送达!

    前言

    本篇文章详细介绍一下listen指令的解析,以及socket的创建过程。
    listen的内容太多了,并且牵涉到后面的很多地方,所以只能再一次的回到这里仔细的学习listen指令的解析过程。首先要参考listennginx官方文档,知道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,我们下面分析一下这个函数。

    使用到的结构体

    ngx_url_tngx_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];
    42ngx_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() 函数的辅助字段使用。我们在后续的分析过程中,可以结合上面的图片进行学习

    ngx_url_t结构体使用情况ngx_url_t结构体使用情况

    内部布局总结

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

    1listen   8080
    2listen   *:8080
    3listen    127.0.0.1:8080
    4listen     localhost:8080

    对应的图片如下:

    图1图1 图2图2 图3图3 图4图4

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

    郑尔多斯郑尔多斯
  • 相关阅读:
    什么是 bean 的自动装配?
    什么是 Spring 的内部 bean?
    什么是 Spring 的 MVC 框架?
    Spring AOP and AspectJ AOP 有什么区别?
    解释 JDBC 抽象和 DAO 模块?
    volatile 类型变量提供什么保证?
    一个 Spring Bean 定义 包含什么?
    什么是 Spring MVC 框架的控制器?
    使用 Spring 访问 Hibernate 的方法有哪些?
    什么是 Callable 和 Future?
  • 原文地址:https://www.cnblogs.com/zhengerduosi/p/10393702.html
Copyright © 2011-2022 走看看