zoukankan      html  css  js  c++  java
  • nginx listen指令浅析之add listen

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

    nginx listen指令浅析之add listen

    前言

    我们在上篇文章中介绍了address:port的解析过程,这篇文章继续讲解解析listen指令的后续过程。
    解析listen指令的函数是 ngx_http_core_listen(),这个函数的前半部分是解析address:port ,我们在上篇文章中介绍过。后面紧接着就是解析各种 default_server, recvbuf 等字段,这些很简单,只需要设置ngx_http_listen_opt_t结构体中的相应字段即可,主要的功能在于下面的 ngx_http_add_listen() 函数。

    函数功能

    我们知道ngx_http_core_main_conf_t结构体中有一个ports字段,这个字段是一个ngx_http_conf_port_t类型的数组。这个数组保存了当前http块指令监听的所有端口。ngx_http_add_listen() 函数就是把监听的端口信息保存到这个数组中。

    数据结构体

    牵涉到的结构体如下:
    端口数据结构:

    1typedef struct {
    2    // 协议族类型,我们只讨论IPV4,所以这里是AF_INET
    3    ngx_int_t    family;
    4    // 监听的端口号
    5    in_port_t    port;
    6    /* array of ngx_http_conf_addr_t */
    7    ngx_array_t   addrs;     
    8ngx_http_conf_port_t;

    address数据结构:

     1typedef struct {
    2    ngx_http_listen_opt_t      opt;
    3
    4    ngx_hash_t                 hash;
    5// 下面三个字段都是哈希表,用来保存当前address:port对应的server_name
    6// 其中key是server_name对应的字符串
    7// value 是 ngx_http_core_srv_conf_t 结构体
    8    ngx_hash_wildcard_t       *wc_head;
    9    ngx_hash_wildcard_t       *wc_tail;
    10
    11    ngx_uint_t                 nregex;
    12    ngx_http_server_name_t    *regex;
    13
    14    /* the default server configuration for this address:port */
    15    ngx_http_core_srv_conf_t  *default_server;
    16     /* array of ngx_http_core_srv_conf_t */
    17    ngx_array_t                servers; 
    18ngx_http_conf_addr_t;

    源码分析

    下面的ngx_http_add_listen()源码删除了IPV6Unix Domain代码,只保留了IPV4相关的代码,如下:

     1ngx_int_t
    2ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    3    ngx_http_listen_opt_t *lsopt)
    4{
    5    in_port_t                   p;
    6    ngx_uint_t                  i;
    7    struct sockaddr            *sa;
    8    struct sockaddr_in         *sin;
    9    ngx_http_conf_port_t       *port;
    10    ngx_http_core_main_conf_t  *cmcf;
    11
    12    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    13
    14// 为 ngx_http_core_main_conf_t 结构的 port 分配空间,保存所有的端口
    15    if (cmcf->ports == NULL) {
    16        cmcf->ports = ngx_array_create(cf->temp_pool, 2,
    17                                       sizeof(ngx_http_conf_port_t));
    18    }
    19
    20    sa = &lsopt->u.sockaddr;
    21
    22    switch (sa->sa_family) {
    23
    24    default/* AF_INET */
    25        sin = &lsopt->u.sockaddr_in;
    26        p = sin->sin_port; // 拿到当前listen指令的端口号
    27        break;
    28    }
    29
    30    port = cmcf->ports->elts;
    31// 遍历所有的port,查看该端口号是否已经存在
    32    for (i = 0; i < cmcf->ports->nelts; i++) {
    33     // 这里的意思就是:只有当协议版本和端口号都相同的话才算是同一个端口号
    34        if (p != port[i].port || sa->sa_family != port[i].family) {
    35            continue;
    36        }
    37
    38        /* a port is already in the port list */
    39// 如果执行到这里,说明之前已经存在了一个协议版本和端口号都相同的listen,这样的话我们应该把当前监听的
    40// address 加入到已有的port->addrs 数组中,统一管理当前端口对应的 address
    41        return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
    42    }
    43
    44    /* add a port to the port list */
    45 // 如果当前端口不存在,那么就向 ngx_http_core_main_conf_t->port 中添加一个元素
    46// 表示一个我们监听了一个新的端口
    47    port = ngx_array_push(cmcf->ports);
    48    if (port == NULL) {
    49        return NGX_ERROR;
    50    }
    51
    52    port->family = sa->sa_family;// port中保存了协议类型,因为我们区分不同的协议
    53    port->port = p;
    54    port->addrs.elts = NULL;
    55   // 将当前listen的address加入到port->addr中
    56    return ngx_http_add_address(cf, cscf, port, lsopt);
    57}

    其实这个函数很简单,具体可以分为以下几个步骤:

    • 遍历所有的ports,查找是否存在相同协议类型和端口的元素
    • 如果存在上述的元素,就调ngx_http_add_addresses()
    • 如果不存在上述元素,就调用ngx_http_add_address()

    上面的两个函数名字也很有意思,ngx_http_add_addresses()使用了复数形式的addresses(),其实也说明了相同的元素已经存在,这里需要添加多个元素。ngx_http_add_address()使用单数形式的address,说明添加的应该是第一个元素。

    端口不存在

    上面我们分析过,如果端口不存在,那么会调用ngx_http_add_address()函数,代码如下:

     1//这个函数是将一个listen的addr加入到port->addrs数组中
    2static ngx_int_t
    3ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    4    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)

    5
    {
    6    ngx_http_conf_addr_t  *addr;
    7
    8    if (port->addrs.elts == NULL) {
    9        if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
    10                           sizeof(ngx_http_conf_addr_t))
    11            != NGX_OK)
    12        {
    13            return NGX_ERROR;
    14        }
    15    }
    16
    17// 下面的代码就是将listen的address添加到port->addrs数组中.
    18// 然后初始化这个元素
    19    addr = ngx_array_push(&port->addrs);
    20    addr->opt = *lsopt;
    21    addr->hash.buckets = NULL;
    22    addr->hash.size = 0;
    23    addr->wc_head = NULL;
    24    addr->wc_tail = NULL;
    25#if (NGX_PCRE)
    26    addr->nregex = 0;
    27    addr->regex = NULL;
    28#endif
    29//先给default_server赋一个默认值,也即是当前address:port对应的ngx_http_core_srv_conf_t结构体
    30// 下面的 ngx_http_add_server会根据 ngx_http_listen_opt_t 结构体的值重新给 default_server赋值.
    31// 如果我们在listen后面没有设置default_server指令,那么所有相同 address:port 的server的第一个server就是default_server
    32    addr->default_server = cscf;
    33    addr->servers.elts = NULL;
    34 // 下面的函数是将一个ngx_http_core_srv_conf_t 结构体添加到 addr->servers 中
    35// 由于同一个 address:port 可以对应于多个server,所以这里通过 port->addr->server字段
    36//将相同的 address:port 对应的所有虚拟主机关联起来
    37    return ngx_http_add_server(cf, cscf, addr);
    38}

    添加server

    每个address:port都可能对应多个serverngx_http_add_server函数就是serveraddress:port对应起来。

     1static ngx_int_t
    2ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    3    ngx_http_conf_addr_t *addr)

    4
    {
    5    ngx_uint_t                  i;
    6    ngx_http_core_srv_conf_t  **server;
    7
    8    if (addr->servers.elts == NULL) {
    9        if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
    10                           sizeof(ngx_http_core_srv_conf_t *))
    11            != NGX_OK)
    12        {
    13            return NGX_ERROR;
    14        }
    15
    16    } else {
    17        server = addr->servers.elts;
    18        for (i = 0; i < addr->servers.nelts; i++) {
    19            if (server[i] == cscf) {
    20// 这一部分的意思就是:防止同一个server块内有两个listen指令
    21                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    22                                   "a duplicate listen %s", addr->opt.addr);
    23                return NGX_ERROR;
    24            }
    25        }
    26    }
    27
    28// 下面的代码实在是没有什么意思,就是将 listen 指令所在的 ngx_http_core_srv_conf_t 
    29// 结构体添加到 addr->servers 数组中
    30    server = ngx_array_push(&addr->servers);
    31    *server = cscf;
    32
    33    return NGX_OK;
    34}

    端口已存在

    上面我们说过,如果相同的端口已经存在,那么就会调用ngx_http_add_addresses()函数将当前的端口添加到相应的数组元素中。我们下面看一下这个函数。

    再次重申:执行ngx_http_add_addresses的时候,表示当前listenport已经存在。

     1static ngx_int_t
    2ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    3    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)

    4
    {
    5    u_char                *p;
    6    size_t                 len, off;
    7    ngx_uint_t             i, default_server;
    8    struct sockaddr       *sa;
    9    ngx_http_conf_addr_t  *addr;
    10
    11    /*
    12     * we cannot compare whole sockaddr struct's as kernel
    13     * may fill some fields in inherited sockaddr struct's.
    14* 不能比较整个sockaddr结构体,因为内核可能在sockaddr中填充了其他的字段。
    15* 我的理解:不同版本的内核,sockaddr结构体中可能包含不同的字段,所以不能比较整个结构体,只能比较一些必然会存在的字段.
    16* 这应该也是为了兼容性
    17     */

    18
    19    sa = &lsopt->u.sockaddr;
    20
    21    switch (sa->sa_family) {
    22
    23    default/* AF_INET */
    24// off 字段指向了 sockaddr_in 中 sin_addr 的起始地址
    25// len 字段说明了 sin_addr 的长度
    26// 下面我们要比较相同的address是否已经存在了
    27        off = offsetof(struct sockaddr_in, sin_addr);
    28        len = 4;// sin_addr的长度
    29        break;
    30    }
    31
    32    p = lsopt->u.sockaddr_data + off;
    33
    34    addr = port->addrs.elts;
    35
    36    for (i = 0; i < port->addrs.nelts; i++) {
    37 // 遍历当前port所有的addrs,查找是否存在相同 address 
    38        if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) {
    39            continue;
    40        }
    41
    42        /* the address is already in the address list */
    43 // 如果address已经存在,也就是说 address 和 port 全部相同
    44// 那么我们就只需把 ngx_http_core_srv_conf_t 添加到 addrs->servers
    45// 数组中即可
    46        if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
    47            return NGX_ERROR;
    48        }
    49
    50        /* preserve default_server bit during listen options overwriting */
    51// 看一下当前遍历到的listen指令是否设置了 default_server 
    52        default_server = addr[i].opt.default_server;
    53
    54        if (lsopt->set) {
    55// 这里的作用我们在 关于listen指令中的bind和set.note 文章中已经说过了
    56            if (addr[i].opt.set) {
    57                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    58                        "duplicate listen options for %s", addr[i].opt.addr);
    59                return NGX_ERROR;
    60            }
    61
    62            addr[i].opt = *lsopt;
    63        }
    64
    65        /* check the duplicate "default" server for this address:port */
    66
    67        if (lsopt->default_server) {
    68
    69            if (default_server) {
    70                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    71                        "a duplicate default server for %s", addr[i].opt.addr);
    72                return NGX_ERROR;
    73            }
    74
    75            default_server = 1;
    76            addr[i].default_server = cscf;
    77        }
    78
    79        addr[i].opt.default_server = default_server;
    80        return NGX_OK;
    81    }
    82
    83    /* add the address to the addresses list that bound to this port */
    84    // 如果执行到这里,说明该address尚不存在,所以新建一个address
    85    return ngx_http_add_address(cf, cscf, port, lsopt);
    86}

    函数的关系

    上面的几个函数错综复杂,容易弄混,我对这几个函数的功能进行了一些总结。

    ngx_http_add_addresses()函数的功能呢:

    遍历当前端口addrs数组的所有元素, 查看是否有addrs元素和当前的address相同
    1、如果不存在相同的address元素,则调用ngx_http_add_address()完成如下功能:

    • 将当前listen指令的address作为一个新元素添加到port-&gt;addrs数组中
    • 调用 ngx_http_add_server() 函数完成如下功能
      如果调用 ngx_http_add_server() 则说明 addressport都是相同的,那么我们应该将当前 address:port 对应的 ngx_http_core_srv_conf_t 结构体添加到 port-&gt;addrs-&gt;servers 数组中。

    2、如果存在相同的address元素,那么就调用 ngx_http_add_server()函数完成功能。

    测试用例

    该配置文件仅用于测试listen指令

     1user root;
    2daemon on;
    3worker_processes  1;
    4master_process on;
    5
    6events {
    7    use epoll;
    8    worker_connections  1024;
    9    multi_accept on;
    10    accept_mutex on;
    11}
    12
    13
    14http{
    15
    16      # server_1,和server_2监听的 ip:port 相同
    17    server {
    18       listen  127.0.0.1:8088;
    19       server_name first;
    20       location / {
    21            root   first;
    22        }
    23    }
    24
    25    # server_2,和server_1监听的 ip:port 相同
    26    server {
    27        listen  127.0.0.1:8088;
    28        server_name second;
    29        location / {
    30            root   second;
    31        }
    32    }
    33
    34    # 先配置一个虚拟ip: ifconfig eth0:1 192.168.10.5 netmask 255.255.255.0
    35    # server_3,和server_1监听的 port 相同,但是ip不同
    36    server {
    37        listen  192.168.10.5:8088;
    38        server_name third;
    39        location / {
    40            root   third;
    41        }
    42
    43    }
    44
    45     # server_4,和server_3监听的 ip:port 相同
    46    server {
    47       listen  192.168.10.5:8088;
    48       server_name fourth;
    49       location / {
    50                root  fourth;
    51       }
    52
    53    }
    54
    55
    56    # server_5
    57    server {
    58        listen  127.0.0.1:8089;
    59        server_name fiveth;
    60        location /{
    61            root   fiveth;
    62        }
    63
    64    }
    65
    66    # server_6 与server_5监听的端口相同,ip不同
    67    server {
    68        listen  192.168.10.5:8089;
    69        server_name sixth;
    70        location /{
    71            root sixth;
    72        }
    73
    74    }
    75
    76
    77   # server_7 与server_5监听的端口相同,但是监听全部ip
    78   server {
    79        listen  8089;
    80        server_name seventh;
    81        location / {
    82                root   seventh;
    83        }
    84   }
    85}

    测试结果

    listen内存布局listen内存布局

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

    郑尔多斯郑尔多斯
  • 相关阅读:
    OPENCV图像变换-1
    OPENCV形态学操作1
    OPENCV基本滤波算法
    OSX下编译安装opencv3.1.0与opencv_contrib_master
    iOS8学习笔记-构建多视图应用程序
    iOS8学习笔记2--autolayout
    iOS学习笔记1--在xcode6以上的版本中不使用storyboard以及部分控件使用
    Objective-c学习笔记3
    objective-c学习笔记2
    objective-c学习笔记
  • 原文地址:https://www.cnblogs.com/zhengerduosi/p/10404657.html
Copyright © 2011-2022 走看看