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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

程序员的思维修炼

程序员的思维修炼

Andy Hunt / 崔康 / 人民邮电出版社 / 2010-12-10 / 39.00元

本书解释了为什么软件开发是一种精神活动,思考如何解决问题,并就开发人员如何能更好地开发软件进行了评论。书中不仅给出了一些理论上的答案,同时提供了大量实践技术和窍门。 本书供各层次软件开发人员阅读。一起来看看 《程序员的思维修炼》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具