nginx 源码分析:http 模块解析 listen 配置

http 模块解析 listen 配置

启动流程 这篇文章中,我提到 ngx_init_cycle 函数最后会调用 ngx_open_listening_sockets 函数打开所有监听的端口,会创建所有的 listening socket 。实际上,nginx 将需要创建的 listening socket 用 ngx_listening_t 结构体表示。查看 ngx_open_listening_sockets 函数代码我们就知道,它会遍历 cycle 中所有的 ngx_listening_t 结构,依次调用 socket、bind、listen 等系统调用完成打开端口的操作。

http 模块配置解析 这篇文章中,我提到处理 http{} 配置的函数 ngx_http_block 最后会调用 ngx_http_optimize_servers 函数创建 ngx_listening_t 结构。

nginx 创建 ngx_listening_t 结构不是一次性创建的,而是由 http 模块先创建一些辅助的结构 ngx_http_listen_opt_t ,然后由这个结构创建 ngx_listening_t 结构。而 ngx_http_listen_opt_t 结构是 ngx_http_core_module 在处理 listen 配置时创建的。所以我们先来分析 listen 配置的处理函数 ngx_http_core_listen

这篇文章就让我们看下创建 ngx_listening_t 结构的完整流程。为了便于说明代码,我们使用一个如下的配置文件:

events {

}

http {
    server {
        listen 80;
        location / {

        }
    }

    server {
        listen 172.16.1.2:80;
        location / {

        }
    }

    server {
        listen 172.16.1.3:8080;
        location / {

        }
    }
}

这个配置文件使得 nginx 监听 0.0.0.0:80,172.16.1.2:80,172.16.1.3:8080。

ngx_http_core_listen

ngx_http_core_listen 函数的流程中涉及到的数据结构有好几个,我画了一张图,显示的就是这个函数处理上面的配置时创建的一些数据结构:
数据结构每个监听的 http 端口由 ngx_http_conf_port_t 结构表示。
每个监听的 IP地址和端口用 ngx_http_conf_addr_t 结构表示。其中的 opt 成员是 ngx_http_listen_opt_t 结构体,里面保存了监听的 IP地址和端口号。servers 成员是 ngx_http_core_srv_conf_t 结构体数组,对应这个 listen 配置所在的 server{} 配置。

我们给出的配置文件中, nginx 监听 0.0.0.0:80,172.16.1.2:80,172.16.1.3:8080,每个监听的地址属于一个 server{}。所以会创建 2 个 ngx_http_conf_port_t 结构,3 个 ngx_http_conf_addr_t 结构。

接下来看 ngx_http_core_listen 的代码就容易了。以下是解析 listen 80; 这个配置的流程:

    cscf->listen = 1; /* server{} 有 listen 配置指令 */

    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) {
        if (u.err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "%s in \"%V\" of the \"listen\" directive",
                               u.err, &u.url);
        }

        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;

    /* listen 80; 或 listen *; 或 listen *:80  */
    lsopt.wildcard = u.wildcard;

    (void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen, lsopt.addr,
                         NGX_SOCKADDR_STRLEN, 1);

它首先调用 ngx_parse_url 函数解析第一个参数,解析出参数中的 IP地址(0.0.0.0)和端口号(80),然后将 IP地址和端口号复制到 ngx_http_listen_opt_t 结构的局部变量 lsopt 中。

之后处理 listen 配置后面的参数:

    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;
        }

        if (ngx_strcmp(value[n].data, "bind") == 0) {
            lsopt.set = 1;
            lsopt.bind = 1;
            continue;
        }
    	
    	/* 省略部分代码 */
    }

处理完所有的参数后它调用 ngx_http_add_listen 函数:

    if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
        return NGX_CONF_OK;
    }

ngx_http_add_listen

ngx_http_add_listen 函数代码如下:

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    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);
}

它获取在处理 http{} 配置时创建的 ngx_http_core_main_conf_t 结构。由于是第一次进入函数,它先初始化 ports 数组。然后创建一个 ngx_http_conf_port_t 结构,赋值 port 成员的值为 80 ,将它加入数组中。最后调用 ngx_http_add_address 函数。

ngx_http_add_address

ngx_http_add_address 函数第一次进入时会初始化 addrs 数组,创建一个 ngx_http_conf_addr_t 结构,赋值 opt 成员,最后调用 ngx_http_add_server 函数:

    if (port->addrs.elts == NULL) {
        if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
                           sizeof(ngx_http_conf_addr_t))
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    }
    
    addr = ngx_array_push(&port->addrs);
    if (addr == NULL) {
        return NGX_ERROR;
    }

    addr->opt = *lsopt;
    addr->hash.buckets = NULL;
    addr->hash.size = 0;
    addr->wc_head = NULL;
    addr->wc_tail = NULL;
    addr->default_server = cscf;
    addr->servers.elts = NULL;

    return ngx_http_add_server(cf, cscf, addr);

ngx_http_add_server

ngx_http_add_server 函数的套路也是一样,第一次进入时初始化 servers 数组,将 ngx_http_core_srv_conf_t 结构体加入其中:

    if (addr->servers.elts == NULL) {
        if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
                           sizeof(ngx_http_core_srv_conf_t *))
            != NGX_OK)
        {
            return NGX_ERROR;
        }

    } else {
        server = addr->servers.elts;
        /* 遍历相同的端口和IP地址的 server{} 配置结构体 */
        for (i = 0; i < addr->servers.nelts; i++) {
            if (server[i] == cscf) {
                /* server{}下有两条重复的 listen 指令,报错 */
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "a duplicate listen %s", addr->opt.addr);
                return NGX_ERROR;
            }
        }
    }

    server = ngx_array_push(&addr->servers);
    if (server == NULL) {
        return NGX_ERROR;
    }

    *server = cscf;

在解析 listen 172.16.1.2:80; 这条配置时,ngx_http_core_listen 函数还是调用 ngx_http_add_listen 函数,在 ngx_http_add_listen 函数中由于 cmcf->ports 数组中已经存在 80 端口,所以它会调用 ngx_http_add_addresses 函数。
这个函数会遍历 port->addrs 数组,如果发现监听的 IP 地址已经在其中,则调用 ngx_http_add_server 函数将 ngx_http_core_srv_conf_t 结构体加入addr->servers 数组中。如果监听的 IP 地址是新的,则调用 ngx_http_add_address 函数。

在解析 listen 172.16.1.3:8080; 这条配置时,和解析 listen 80; 配置的流程一样。最终就创建了图中所示的那些结构。

ngx_http_optimize_servers

在解析完 http{} 配置后,ngx_http_block 调用 ngx_http_optimize_servers 函数。
这个函数遍历 ports 数组每一项,将每一项包含的 ngx_http_conf_addr_t 结构体数组排序,使得通配匹配的地址排在后面。在我们的例子中,处理 80 端口时,排序后的地址为 172.16.1.2,0.0.0.0。然后调用 ngx_http_init_listening 函数。

ngx_http_init_listening

这个函数遍历所有的 ngx_http_conf_addr_t 结构,调用 ngx_http_add_listening 函数 。

ngx_http_add_listening

ngx_http_add_listening 调用 ngx_create_listening 函数创建 ngx_listening_t 结构,将它加入 cycle 中的 listening 成员中。它的代码如下:

    ls = ngx_create_listening(cf, &addr->opt.sockaddr.sockaddr,
                              addr->opt.socklen);
    if (ls == NULL) {
        NGX_LOG_CALL_LEVEL_DEC(cf->log);
        return NULL;
    }

    ls->addr_ntop = 1;

    ls->handler = ngx_http_init_connection; /* 重要 */

它将 ngx_listening_t 结构中的 handler 成员赋值为 ngx_http_init_connection 函数。这样,当 listening socket 上有读事件时,ngx_event_accept 函数在建立连接后就会调用 ngx_http_init_connection 函数处理。

ngx_http_init_listening

创建了 ngx_listening_t 结构后,ngx_http_init_listening 分配一个 ngx_http_port_t 结构,使 ngx_listening_t 结构中的 servers 成员指向它。然后调用 ngx_http_add_addrs 函数为每个监听的IP地址创建一个 ngx_http_in_addr_t 结构。

虽然我们配置文件中配置监听 0.0.0.0:80,172.16.1.2:80,172.16.1.3:8080。但实际上 ngx_http_init_listening 函数只创建了两个 ngx_listening_t 结构,最终只会有两个 listening socket。这是因为只要有一个端口上绑定了通配地址,就只需要创建一个绑定通配地址的 listening socket。
数据结构
最终 ngx_http_optimize_servers 函数创建的一些数据结构的关系如上图所示。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值