• 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

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

    郑尔多斯郑尔多斯
  • 相关阅读:
    数据结构--链表基础练习题
    LeetCode 10.28每日一题1207. 独一无二的出现次数【简单】
    数据结构--链表
    LeetCode 10.25每日一题845. 数组中的最长山脉【中等】
    LeetCode 10.22每日一题763. 划分字母区间【中等】
    解决map热点与uni-app中map标签冲突的问题。(Vue Render函数应用)
    【Codeforces 1329A】Dreamoon Likes Coloring
    【Codeforces Alpha Round #20 C】Dijkstra?
    【 Educational Codeforces Round 93 (Rated for Div. 2) D】Colored Rectangles
    【Codeforces Round #643 (Div. 2) C】Count Triangles
  • 原文地址:https://www.cnblogs.com/zhengerduosi/p/10393702.html
Copyright © 2020-2023  润新知