文章目录

  • 1. ngx_queue_t
  • 2. ngx_array_t
  • 3. ngx_rbtree_t
  • 4. ngx_hash_t
  • 5 ngx_hash_wildcard_t
  • 6. ngx_hash_combined_t
  • 7. ngx_hash_keys_arrays_t


Nginx的高级数据包括ngx_queue_t, ngx_array_t, ngx_list_t, ngx_rbtree_t, ngx_radix_tree_t, ngx_hash_t

1. ngx_queue_t

ngx_queue_t双向链表是Nginx提供的轻量级链表容器,与Nginx的内存池无关,因此这个链表不会负责分配内存来存放元素,这个数据结构仅仅把已经分配好的内存元素使用双向链表连接起来,所以对每个用户来说,只需要增加两个指针的空间即可,消耗的内存很少。同时,ngx_queue_t还提供了一个非常简易的插入排序法。相对于Nginx其他顺序容器,它的优势在于:

  • 实现了排序功能
  • 非常轻量级,是一个纯粹的双向链表,它不负责链表元素所占内存的分配,与Nginx封装的ngx_pool_t内存池完全无关。
  • 支持两个链表间的合并
typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

它的实现非常简单,仅有两个成员prev, next。

实现访问API:

ngx_queue_init(q)                  //初始化链表  q为空
ngx_queue_empty(h)                 //检测链表是否为空
ngx_queue_insert_head(h, x)        //将x插入到h的头部
ngx_queue_insert_tail(h, x)        //将x插入到h的尾部
ngx_queue_head(h)                  //返回链表h中的第一个元素的结构体指针
ngx_queue_last(h)                  //返回链表h中的最后一个元素的结构体指针
ngx_queue_sentinel(h)              //返回链表容器结构体的指针
ngx_queue_remove(x)                //移除x结构体元素
ngx_queue_split(h, q, n)           // 以元素q为界分为h和n两个链表,其中h是前半部分(不包括q),n是后半部分(包括q)
ngx_queue_add(h, n)                //合并链表,将n添加到h的尾部
ngx_queue_middle(h)                //返回链表的中心元素,假如有N个,返回N/2+1个元素
ngx_queue_sort(h, compfunc)        //使用插入排序对链表进行排序,compfunc需要自己实现

对于链表中的每一个元素,其类型可以是任意的struct结构体,但这个结构体中必须要有一个ngx_queue_t 类型的成员,在向链表容器中添加、删除时都是使用结构体中的ngx_queue_t类型成员的指针。当ngx_queue_t作为链表的元素成员使用时,他具有下面4种用法:

ngx_queue_next(q)                  //返回链表q的下一个元素
ngx_queue_prev(q)                  //返回q的上一个元素
ngx_queue_data(q, type, link)      //返回q元素所属结构体中可在任意位置包含ngx_queue_t的地址
ngx_queue_insert_after(q, x)       //和ngx_queue_insert_head相同,因为是双向链表,没有头尾之分

Test SampleCode:

#include <stdio.h>
#include <string.h>

#include "ngx_config.h"

#include "ngx_core.h"


#include "ngx_list.h"
#include "ngx_palloc.h"
#include "ngx_string.h"
#include "ngx_queue.h"

ngx_queue_t queueContainer;

typedef struct {
    u_char *str;
    ngx_queue_t qEle;
    int num;
}TestNode;

ngx_int_t compTestNode(const ngx_queue_t* a, const ngx_queue_t *b) {
    TestNode *aNode = ngx_queue_data(a, TestNode, qEle);
    TestNode *bNode = ngx_queue_data(b, TestNode, qEle);

    return aNode->num > bNode->num;
}

volatile ngx_cycle_t *ngx_cycle;
 
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,
			ngx_err_t err, const char *fmt, ...)
{



}

int main() {
    TestNode node[5];

    ngx_queue_init(&queueContainer);

    for (int i = 0; i < 5; i++) {
        node[i].num = i;
        
    }
    ngx_queue_insert_tail(&queueContainer, &node[1].qEle);
    ngx_queue_insert_tail(&queueContainer, &node[4].qEle);
    ngx_queue_insert_tail(&queueContainer, &node[0].qEle);
    ngx_queue_insert_tail(&queueContainer, &node[2].qEle);
    ngx_queue_insert_tail(&queueContainer, &node[3].qEle);

    ngx_queue_sort(&queueContainer, compTestNode);

    ngx_queue_t *q;
    for (q = ngx_queue_head(&queueContainer); q != ngx_queue_sentinel(&queueContainer); q = ngx_queue_next(q)) {
        TestNode *eleNode = ngx_queue_data(q, TestNode, qEle);
        printf("%d\n", eleNode->num);
    }

    return 0;
}

编译:

gcc -o ngx_queue_main ngx_queue_main.c  -I ../../nginx-1.16.1/src/core/ -I ../../nginx-1.16.1/objs/  -I ../../nginx-1.16.1/src/os/unix/ -I ../../pcre-8.44/ -I ../../nginx-1.16.1/src/event/ ../../nginx-1.16.1/objs/src/core/ngx_list.o ../../nginx-1.16.1/objs/src/core/ngx_string.o ../../nginx-1.16.1/objs/src/core/ngx_palloc.o ../../nginx-1.16.1/objs/src/os/unix/ngx_alloc.o../../nginx-1.16.1/objs/src/core/ngx_queue.o

2. ngx_array_t

ngx_array_t 是一个顺序的动态数组,在Nginx大量使用,支持达到数组容量上限时动态改变数组的大小。ngx_array_t 和C++ STL中的vector很类似,它内置了Nginx封装的内存池,因此,他分配的内存可以在内存池中申请得到。具备下面几个特点:

  • 访问速度快
  • 允许元素个数具备不确定性
  • 负责元素占用内存的分配,这些内存由内存池统一管理
typedef struct {
    void        *elts;    //指向数组的首地址
    ngx_uint_t   nelts;   //数组中已经使用的元素个数
    size_t       size;   //每个元素占用的内存大小
    ngx_uint_t   nalloc; // 当前数组中能够容纳元素个数的总大小
    ngx_pool_t  *pool;    //内存池对象
} ngx_array_t;

提供了如下API:

ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size); //初始化1个已经存在的动态数组,并预分配n个大小为size的内存空间
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size); //创建1个动态数组,并预分配n个大小为size的内存空间
ngx_array_destroy(ngx_array_t *a);                     // 销毁数组 和create配对使用
ngx_array_push(ngx_array_t *a);                        // 向当前a动态数组中添加1个元素,返回的是这个新添加元素的地址
ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);        //向当前a动态数组中添加n个元素,返回的是新添加这批元素中第一个元素的地址

动态数组的扩容方式:

ngx_array_pushngx_array_push_n都可能引发扩容操作,ngx_array_push 方法将会申请 ngx_array_t结构体中size字节的大小,而ngx_array_push_n方法将会申请n个size字节大小的内存。每次扩容的大小将受制于内存池的以下两种情形:

  • 如果当前内存池中剩余的空间的大于或者等于本次需要新增的空间,那么本次扩容将只扩容新增的空间。例如: 对于ngx_array_push来说,就是扩充1个元素,而对于ngx_array_push_n来说,就是扩充n个元素。
  • 如果当前内存池中剩余的空间小于本次需要新增的空间,那么对ngx_array_push方法来说,会将原先动态数组的容量扩容一倍,而对于ngx_array_push_n 来说,如果参数n小于原先动态数组的容量,将会扩容一倍;如果参数n大于原先动态数组的容量,这时会分配2n大小的空间,扩容超过一倍,此时动态数组会被分配在全新的内存块上,会把原先的元素复制到新的动态数组中。

SampleCode:

#include <stdio.h>
#include <string.h>

#include "ngx_config.h"

#include "ngx_core.h"


#include "ngx_list.h"
#include "ngx_palloc.h"
#include "ngx_string.h"



#define N		10

typedef struct _key {
	int id;
	char name[32];
} Key;

volatile ngx_cycle_t *ngx_cycle;
 
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,
			ngx_err_t err, const char *fmt, ...)
{



}


void print_array(ngx_array_t *array) {

	Key *key = array->elts;

	int i = 0;
	for (i = 0;i < array->nelts;i ++) {
		printf("%s .\n", key[i].name);
	}

}

int main() {

	ngx_pool_t *pool = ngx_create_pool(1024, NULL);

	ngx_array_t *array = ngx_array_create(pool, N, sizeof(Key));

	int i = 0;
	Key *key = NULL;
	for (i = 0;i < 25;i ++) {

		key = ngx_array_push(array);
		key->id = i+1;
		sprintf(key->name, "array %d", key->id);
		
	}

	key = ngx_array_push_n(array, 10);
	for (i = 0;i < 10;i ++) {
		key[i].id = 25+i+1;
		sprintf(key[i].name, "array %d", key[i].id);
	}

	print_array(array);

}

编译:

gcc -o ngx_array_main ngx_array_main.c -I ../../nginx-1.16.1/src/core/ -I ../../nginx-1.16.1/objs/  -I ../../nginx-1.16.1/src/os/unix/ -I ../../pcre-8.44/ -I ../../nginx-1.16.1/src/event/  ../../nginx-1.16.1/objs/src/core/ngx_string.o ../../nginx-1.16.1/objs/src/core/ngx_palloc.o ../../nginx-1.16.1/objs/src/os/unix/ngx_alloc.o  ../../nginx-1.16.1/objs/src/core/ngx_array.o

3. ngx_rbtree_t

ngx_rbtree_t是使用红黑树实现的一种关联容器,Nginx模块的核心(定时器管理、文件缓存模块等)在需要快速检索、查找的场合下都使用了ngx_rbtree_t容器。

typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

struct ngx_rbtree_node_s {
    ngx_rbtree_key_t       key;              //uint的关键字
    ngx_rbtree_node_t     *left;             //左子节点
    ngx_rbtree_node_t     *right;           //右子节点
    ngx_rbtree_node_t     *parent;           //父节点
    u_char                 color;           //节点的颜色, 0:黑色 1:红色
    u_char                 data;           //1字节的节点数据 
};


typedef struct ngx_rbtree_s  ngx_rbtree_t;
//为解决不同节点含有相同关键字的元素冲突问题,红黑树设置了ngx_rbtree_insert_pt指针,这样可灵活地添加冲突元素
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root; //指向树的根节点
    ngx_rbtree_node_t     *sentinel; //指向NULL哨兵节点
    ngx_rbtree_insert_pt   insert;  //表示红黑树添加元素的函数指针,它决定在添加新节点时的行为究竟是替换还是新增
};

添加数据的三种API:

//向红黑树添加数据节点,每个数据节点的关键字都是唯一的,不存在同一个关键字有多个节点的问题
ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);  
//添加数据节点的关键字可以不是唯一的,但它们是以字符串作为唯一的标识,存放在ngx_str_node_t结构体的str成员中
ngx_rbtree_insert_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
//添加的数据节点的关键字表示时间或者时间差
ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

红黑树的API:

ngx_rbtree_init(tree, s, i)   //初始化红黑树
ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);   //向红黑树添加节点
ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);  //从红黑树中删除节点

在初始化红黑树的时候,需要先分配好保存在红黑树的ngx_rbtree_t结构体,以及ngx_rbtree_node_t类型的哨兵节点,并选择或者自定义ngx_rbree_insert_pt类型的节点添加函数。

Samplecode:

#include <stdio.h>
#include <string.h>

#include "ngx_config.h"

#include "ngx_core.h"


#include "ngx_list.h"
#include "ngx_palloc.h"
#include "ngx_string.h"
#include "ngx_rbtree.h"


volatile ngx_cycle_t *ngx_cycle;
 
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,
			ngx_err_t err, const char *fmt, ...)
{



}


int main() {

	ngx_rbtree_t rbtree;
	ngx_rbtree_node_t sentinel;
	
	ngx_rbtree_init(&rbtree, &sentinel, ngx_str_rbtree_insert_value);

	ngx_str_node_t strnode[10];
	ngx_str_set(&strnode[0].str, "he");
	ngx_str_set(&strnode[1].str, "jon");
	ngx_str_set(&strnode[2].str, "Issac");
	ngx_str_set(&strnode[3].str, "tursom");
	ngx_str_set(&strnode[4].str, "will");

	ngx_str_set(&strnode[5].str, "birate");
	ngx_str_set(&strnode[6].str, "ren");
	ngx_str_set(&strnode[7].str, "stephen");
	ngx_str_set(&strnode[8].str, "ultimate");
	ngx_str_set(&strnode[9].str, "he");

	int i = 0;
	for (i = 0;i < 10;i ++) {
		strnode[i].node.key = i;
		ngx_rbtree_insert(&rbtree, &strnode[i].node);
	}

	ngx_str_t str = ngx_string("will");
	
	ngx_str_node_t *node = ngx_str_rbtree_lookup(&rbtree, &str, 0);
	if (node != NULL) {
		printf(" Exit\n");
	}
}

编译:

gcc -o ngx_array_main ngx_array_main.c -I ../../nginx-1.16.1/src/core/ -I ../../nginx-1.16.1/objs/  -I ../../nginx-1.16.1/src/os/unix/ -I ../../pcre-8.44/ -I ../../nginx-1.16.1/src/event/  ../../nginx-1.16.1/objs/src/core/ngx_string.o ../../nginx-1.16.1/objs/src/core/ngx_palloc.o ../../nginx-1.16.1/objs/src/os/unix/ngx_alloc.o  ../../nginx-1.16.1/objs/src/os/unix/ngx_rbtree.o

4. ngx_hash_t

ngx_hash_t是 nginx 自己的 hash 表的实现。定义和实现位于 src/core/ngx_hash.h|c 中。ngx_hash_t的实现也与数据结构教科书上所描述的 hash 表的实现是大同小异。对于常用的解决冲突的方法有线性探测,二次探测和开链法等。 ngx_hash_t 使用的是最常用的一种,也就是开链法,这也是 STL 中的 hash 表使用的方法 。但是 ngx_hash_t 的实现又有其几个显著的特点:

  • ngx_hash_t不像其他的 hash 表的实现,可以插入删除元素,它只能一次初始化,就构建起整个 hash 表以后,既不能再删除,也不能在插入元素了。
  • ngx_hash_t 的开链并不是真的开了一个链表,实际上是开了一段连续的存储空间,几乎可以看做是一个数组。这是因为ngx_hash_t在初始化的时候,会经历一次预计算的过程,提前把每个桶里面会有多少元素放进去给计算出来,这样就提前知道每个桶的大小了。那么就不需要使用链表,一段连续的存储空间就足够了。这也从一定程度上节省了内存的使用。

从上面的描述,我们可以看出来,这个值越大,越造成内存的浪费。就两步,首先是初始化,然后就可以在里面进行查找了。下面我们详细来看一下。

//ngx_hash_t 的初始化
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);

首先我们来看一下初始化函数。该函数的第一个参数 hinit 是初始化的一些参数的一个集合。 names 是初始化一个 ngx_hash_t所需要的所有 key 的一个数组。而 nelts 就是 key 的个数。下面先看一下 ngx_hash_init_t类型,该类型提供了初始化一个 hash 表所需要的一些基本信息。

typedef struct {
    ngx_hash_t       *hash;
    ngx_hash_key_pt   key;

    ngx_uint_t        max_size;
    ngx_uint_t        bucket_size;

    char             *name;
    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;

/*
hash: 该字段如果为 NULL,那么调用完初始化函数后,该字段指向新创建出来的hash 表。如果该字段不为 NULL,那么在初始的时候,所有的数据被插入了这个字段所指的      hash 表中。
key:指向从字符串生成 hash 值的 hash 函数。 nginx 的源代码中提供了默认的实现函数 ngx_hash_key_lc。
max_size:hash 表中的桶的个数。该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询起来的速度更快。当然,这个值越大,越造成内存的浪费              也越大, (实际上也浪费不了多少)。
bucket_size:每个桶的最大限制大小,单位是字节。如果在初始化一个 hash 表的时候,发现某个桶里面无法存的下所有属于该桶的元素,则 hash 表初始化失败。
name: 该 hash 表的名字。
pool:该 hash 表分配内存使用的 pool。
temp_pool:该 hash 表使用的临时 pool,在初始化完成以后,该 pool 可以被释放和销毁掉。
*/

下面来看一下存储 hash 表 key 的数组的结构 。

typedef struct {
    ngx_str_t         key;
    ngx_uint_t        key_hash;
    void             *value;
} ngx_hash_key_t;

key 和 value 的含义显而易见,就不用解释了。 key_hash 是对 key 使用 hash 函数计算出来的值。 对这两个结构分析完成以后,我想大家应该都已经明白这个函数应该是如何使用了吧。该函数成功初始化一个 hash 表以后,返回 NGX_OK,否则返回 NGX_ERROR

//在 hash 里面查找 key 对应的 value。实际上这里的 key 是对真正的 key(也就是 name)计算出的 hash 值。 len 是 name 的长度。
//如果查找成功,则返回指向 value 的指针,否则返回 NULL
void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);

5 ngx_hash_wildcard_t

nginx 为了处理带有通配符的域名的匹配问题,实现了 ngx_hash_wildcard_t 这样的 hash 表。 他可以支持两种类型的带有通配符的域名。一种是通配符在前的,例如: “ *.abc.com ”,也可以省略掉星号,直接写成.abc.com。这样的 key,可以匹配 www.abc.comqqq.www.abc.com之类的。另外一种是通配符在末尾的,例如: “mail.xxx.* ”,请特别注意通配符在末尾的不像位于开始的通配符可以被省略掉。这样的通配符,可以匹配 mail.xxx.commail.xxx.com.cnmail.xxx.net之类的域名。

有一点必须说明,就是一个 ngx_hash_wildcard_t类型的 hash 表只能包含通配符在前的 key或者是通配符在后的 key。不能同时包含两种类型的通配符的 key。 ngx_hash_wildcard_t类型 变 量 的 构 建 是 通 过 函 数 ngx_hash_wildcard_init 完 成 的 , 而 查 询 是 通 过 函 数ngx_hash_find_wc_head 或者 ngx_hash_find_wc_tail来做的。 ngx_hash_find_wc_head是查询包含通配符在前的 key 的 hash 表的,而 ngx_hash_find_wc_tail是查询包含通配符在后的 key的 hash 表的。

下面详细说明这几个函数的用法:

ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
/*
hinit:  构造一个通配符 hash 表的一些参数的一个集合。
names:  构造此 hash 表的所有的通配符 key 的数组。特别要注意的是这里的 key 已经都是被预处理过的。例如: “*.abc.com”或者“.abc.com”被预处理完成以后,变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”。为什么会被处理这样?这里不得不简单地描述一下通配符 hash 表的实现原理。当构造此类型的 hash 表的时候,实际上是构造了一个 hash 表的一个“链表”,是通过 hash 表中的 key“链接”起来的。比如:对于“*.abc.com”将会构造出 2 个 hash 表,第一个 hash 表中有一个 key 为com 的表项,该表项的 value 包含有指向第二个 hash 表的指针,而第二个 hash 表中有一个表项 abc,该表项的 value 包含有指向*.abc.com 对应的 value 的指针。那么查询的时候,比如查询 www.abc.com 的时候,先查 com,通过查 com 可以找第二级的 hash 表,在第二级 hash 表中,再查找 abc,依次类推,直到在某一级的hash 表中查到的表项对应的 value 对应一个真正的值而非一个指向下一级 hash 表的指针的时候,查询过程结束。 这里有一点需要特别注意的,就是 names 数组中元素的 value 值低两位 bit 必须为 0(有特殊用途)。如果不满足这个条件,这个hash 表查询不出正确结果。
nelts: names 数组元素的个数。
*/

// 该函数执行成功返回 NGX_OK,否则 NGX_ERROR。
//该函数查询包含通配符在前的 key 的 hash 表的。
void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
/*
hwc: hash 表对象的指针。
name: 需要查询的域名,例如: www.abc.com。
len: name 的长度
*/

6. ngx_hash_combined_t

组合类型 hash 表,该 hash 表的定义如下 :

typedef struct {
    ngx_hash_t            hash;
    ngx_hash_wildcard_t  *wc_head;
    ngx_hash_wildcard_t  *wc_tail;
} ngx_hash_combined_t;

从其定义显见,该类型实际上包含了三个 hash 表,一个普通 hash 表,一个包含前向通配符的 hash 表和一个包含后向通配符的 hash 表。 nginx 提供该类型的作用,在于提供一个方便的容器包含三个类型的 hash 表,当有包含通配符的和不包含通配符的一组 key 构建 hash 表以后,以一种方便的方式来查询,你不需
要再考虑一个 key 到底是应该到哪个类型的 hash 表里去查了。构造这样一组合 hash 表的时候,首先定义一个该类型的变量,再分别构造其包含的三
个子 hash 表即可。对于该类型 hash 表的查询, nginx 提供了一个方便的函数 ngx_hash_find_combined

void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len);
/*
hash: 此组合 hash 表对象
key : 根据 name 计算出的 hash 值
name: key的具体内容
len: name的长度
*/

该函数在此组合 hash 表中,依次查询其三个子 hash 表,看是否匹配,一旦找到,立即返回查找结果,也就是说如果有多个可能匹配,则只返回第一个匹配的结果。 返回查询的结果,未查到则返回 NULL。

7. ngx_hash_keys_arrays_t

大家看到在构建一个 ngx_hash_wildcard_t的时候,需要对通配符的哪些 key 进行预处理。这个处理起来比较麻烦。而当有一组 key,这些里面既有无通配符的 key,也有包含通配符的 key 的时候。我们就需要构建三个 hash 表,一个包含普通的 key 的 hash 表,一个包含前向通配符的 hash 表,一个包含后向通配符的 hash 表(或者也可以把这三个 hash 表组合成一个 ngx_hash_combined_t)。在这种情况下,为了让大家方便的构造这些 hash 表, nginx提供给了此辅助类型。该类型以及相关的操作函数也定义在 src/core/ngx_hash.h|c 里。我们先来看一下该类型的定义。

typedef struct {
    ngx_uint_t        hsize;

    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;

    ngx_array_t       keys;
    ngx_array_t      *keys_hash;

    ngx_array_t       dns_wc_head;
    ngx_array_t      *dns_wc_head_hash;

    ngx_array_t       dns_wc_tail;
    ngx_array_t      *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;

/*
hsize : 将要构建的 hash 表的桶的个数。对于使用这个结构中包含的信息构建的三种类型的 hash 表都会使用此参数。
pool: 构建这些 hash 表使用的 pool。
temp_pool: 在构建这个类型以及最终的三个 hash 表过程中可能用到临时 pool。该 temp_pool 可以在构建完成以后,被销毁掉。这里只是存放临时的一些内存消耗。
keys : 存放所有非通配符 key 的数组。
keys_hash: 这是个二维数组,第一个维度代表的是 bucket 的编号,那么keys_hash[i]中存放的是所有的 key 算出来的 hash 值对 hsize 取模以后的值为 i 的 key。假设有 3 个 key,分别是 key1,key2 和 key3 假设 hash 值算出来以后对 hsize 取模的值都是 i,那么这三个 key 的值就顺序存在 keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。该值在调用的过程中用来保存和检测是否有冲突的 key 值,也就是是否有重复。
dns_wc_head: 放前向通配符 key 被处理完成以后的值。比如: “*.abc.com” 被处理完成以后,变成 “com.abc.” 被存放在此数组中。
dns_wc_tail: 存放后向通配符 key 被处理完成以后的值。比如: “mail.xxx.*” 被处理完成以后,变成 “mail.xxx.” 被存放在此数组中。
dns_wc_head_hash: 该值在调用的过程中用来保存和检测是否有冲突的前向通配符的 key值,也就是是否有重复。
dns_wc_tail_hash: 该值在调用的过程中用来保存和检测是否有冲突的后向通配符的 key值,也就是是否有重复。
*/

在定义一个这个类型的变量,并对字段 pool 和 temp_pool 赋值以后,就可以调用函数ngx_hash_add_key 把所有的 key 加入到这个结构中了,该函数会自动实现普通 key,带前向通配符的 key 和带后向通配符的 key 的分类和检查,并将这个些值存放到对应的字段中去,然后就可以通过检查这个结构体中的 keys、 dns_wc_head、 dns_wc_tail 三个数组是否为空,来决定是否构建普通 hash 表,前向通配符 hash 表和后向通配符 hash 表了(在构建这三个类型的 hash 表的时候,可以分别使用 keys、 dns_wc_head、 dns_wc_tail 三个数组)。构建出这三个 hash 表以后,可以组合在一个 ngx_hash_combined_t 对象中,使用
ngx_hash_find_combined 进行查找。或者是仍然保持三个独立的变量对应这三个 hash 表,自己决定何时以及在哪个 hash 表中进行查询。

//一般是循环调用这个函数,把一组键值对加入到这个结构体中。返回 NGX_OK 是加入成功。
//返回 NGX_BUSY 意味着 key 值重复。
ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key,
    void *value, ngx_uint_t flags);

/*
ha :   该结构的对象指针
key:   键值
value: 值
flags: 有两个标志位可以设置, NGX_HASH_WILDCARD_KEY 和 NGX_HASH_READONLY_KEY。同时要设置的使用逻辑与操作符就可以了。 NGX_HASH_READONLY_KEY 被设置的时候 , 在 计 算 hash 值 的 时 候 , key 的 值 不 会 被 转 成 小 写 字 符 , 否 则 会 。NGX_HASH_WILDCARD_KEY 被设置的时候,说明 key 里面可能含有通配符,会进行相应的处理。如果两个标志位都不设置,传 0
*/