nginx listen指令浅析之add listen

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

内容简介:微信公众号:关注可了解更多的关注公众号,有趣有内涵的文章第一时间送达!我们在上篇文章中介绍了

微信公众号:关注可了解更多的 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;     
8} ngx_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; 
18} ngx_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}
复制代码

测试结果

nginx listen指令浅析之add listen
listen内存布局

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

nginx listen指令浅析之add listen
郑尔多斯

以上所述就是小编给大家介绍的《nginx listen指令浅析之add listen》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Computer Age Statistical Inference

Computer Age Statistical Inference

Bradley Efron、Trevor Hastie / Cambridge University Press / 2016-7-21 / USD 74.99

The twenty-first century has seen a breathtaking expansion of statistical methodology, both in scope and in influence. 'Big data', 'data science', and 'machine learning' have become familiar terms in ......一起来看看 《Computer Age Statistical Inference》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具