您的位置:首页 > 理论基础 > 数据结构算法

nginx源码分析2———基础数据结构五(ngx_hash_wildcard_t)

2015-08-12 23:58 639 查看

相关介绍

所谓支持通配符的散列表,就是把基本的散列表(ngx_hash_t)中的元素的关键字,用去除通配符以后的字符作为关键字加入,原理其实没有那么神秘。当然得先熟悉ngx_hash_t,可以看上一节,对ngx_hash_t做深入的了解。

ngx_hash_wildcard_t类型的hash表包含通配符在前的key或者是通配符在后的key。例如,对于关键字为”www.test.*”这样带通配符的情况,直接建立一个专用的后置通配符散列表,存储元素的关键字为”www.test”

源码分析

通配符hash表的结构

这事通配符hash表的结构,可以看到里面包含一个基本hash表。

typedef struct {
ngx_hash_t        hash;
void             *value;
} ngx_hash_wildcard_t;


下面ngx_hash_key_t是我们上一节说过,存储key,value,hashcode的结构体,用于初始化hash表

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


通配符hash表的初始化

ngx_int_t
ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,ngx_uint_t nelts)
{
size_t                len, dot_len;
ngx_uint_t            i, n, dot;
ngx_array_t           curr_names, next_names;
ngx_hash_key_t       *name, *next_name;
ngx_hash_init_t       h;
ngx_hash_wildcard_t  *wdc;

/*在temp_pool上初始化数组curr_names,用于存放ngx_hash_key_t,容量为参数nelts */
if (ngx_array_init(&curr_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK)
{
return NGX_ERROR;
}
//同上,初始化数组(如果不能理解,请看数组那一节)
if (ngx_array_init(&next_names, hinit->temp_pool, nelts,sizeof(ngx_hash_key_t)) != NGX_OK)
{
return NGX_ERROR;
}
//遍历注意最后不是n++
for (n = 0; n < nelts; n = i) {
//====================push name'.' 前面进入cur===============
dot = 0;
//遍历每一个k的每一个字符
for (len = 0; len < names
.key.len; len++){
//如果这个key包含一个dot就break该层for循环
if (names
.key.data[len] == '.') {
dot = 1;
break;
}
}
//push进数组一个元素,返回值是该元素的首地址
name = ngx_array_push(&curr_names);
if (name == NULL) {
return NGX_ERROR;
}
/*初始化该元素(注意该元素是ngx_hash_key_t),是用于初始化哈希表的结构*/
//初始化key
name->key.len = len;
name->key.data = names
.key.data;
//初始化hashcode
name->key_hash = hinit->key(name->key.data, name->key.len);
//初始化value
name->value = names
.value;
/*
如果包含'.' 那么len为'.'所在的pos
如果不包含'.' 那么len为字符的长度。
例如test与test.t
他们的dotlen是一样的
*/
dot_len = len + 1;

if (dot) {
/*
如果包含'.' 那么len包含'.'及'.'以前的长度
如果不包含'.' 那么len为字符的长度。

例如test与test.t分别计算的是
test与test.的长度
分别是4和5  */
len++;
}
//相当于将数组reset(给你一个眼神,自己体会)
next_names.nelts = 0;
//====================push name'.' 后面进入next==============
//理解后的意思是key中含有点,而且不是在最后
if (names
.key.len != len) {
//在next_name压入元素
next_name = ngx_array_push(&next_names);
if (next_name == NULL) {
return NGX_ERROR;
}
//对该元素进行赋值
next_name->key.len = names
.key.len - len;
/*将该字符的起始地址移动'.'后
例如test.com相当与取com放到该数组
*/
next_name->key.data = names
.key.data + len;
//设定hashcode为0
next_name->key_hash = 0;
//value赋值
next_name->value = names
.value;
}
//=================push name
'.' 后面进入next==============
//遍历names
后的所有参数names"数组"的元素
for (i = n + 1; i < nelts; i++) {
/* e.g:
tetetet与tetetetksj将相等
test.com与test.cn将相等
test与test.将相等(len = 4)
test.与test将不相等(len = 5)
*/
if (ngx_strncmp(names
.key.data,
names[i].key.data, len) != 0) {
break;
}

/*如果names
.key没有包含'.',而且后面的key长度比这个key大,而且后面的key的第len+1个字符不是'.'*/
// e.g:  tetetet与tetetetksj将成立
// e.g:  tetetet与tetetet.ksj将不成立
//包含点一定不成立
if (!dot && names[i].key.len > len &&
names[i].key.data[len] != '.')
{
break;
}

next_name = ngx_array_push(&next_names);
if (next_name == NULL) {
return NGX_ERROR;
}
/*
如果主key为test.com
test.com --> com
test.cn --> cn
*/
next_name->key.len = names[i].key.len - dot_len;
next_name->key.data = names[i].key.data + dot_len;
next_name->key_hash = 0;
next_name->value = names[i].value;
}
//===================push end============================
if (next_names.nelts) {
h = *hinit;
h.hash = NULL;
//继续创建通配符hash表
/*
如果主key为test.com
test.com --> com
test.cn --> cn
新的key将是com cn等等字符串。
*/
if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts, next_names.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
//该hash表的首部是ngx_hash_wildcard_t
wdc = (ngx_hash_wildcard_t *) h.hash;
/*
该字符串是刚刚好'.'前面的部分,如果是,就将该value存在通配符的value上
*/
if (names
.key.len == len) {
wdc->value = names
.value;
}
/*设置name后有没有点的标志(也就是name
)最后一个是不是有点的,帮我们分清
1,test
test.c
2,test.
test.c
的区别
*/
name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
} else if (dot) {
//同上(由于内存对齐了,低位都是0)
name->value = (void *) ((uintptr_t) name->value | 1);
}
}
//初始化最初的hash表 使用cur数组
if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts, curr_names.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}


这个是很难理解的,有一点要注意的是所有的key都不包含字符’.’号,点好都是设置标志的。就是names数组中元素的value值低两位bit必须为0,当然也肯定为0,因为内存对齐后,低两位为0,作者为了节约内存,使用了这点内存。如果要完全理解这段代码,应该边画边想,那样会更清晰。

本人不会绘图,现在就传入下面数据绘出一个基本的草图,不喜勿喷

1.   key = test;value = v1
2.   key = test.;value = v2
3.   key = test.a;value = v3
4.   key = test.ab;value = v4
5.   key = test.abc;value = v5
6.   key = abc; value = v6
下面头部的hash可能还对应value结构(ngx_hash_wildcard_t)




通配符hash表的查找

有了上面的数据结构,让我们从ngx_hash_find_wc_head入手去查看一下通配符hash表如何查找。我习惯用key代表里面的name,习惯性的说key-value结构。

对于name的初始化

*.test.com   ------->  com.test.
*.te.com     ------->  com.te.
*.st.com     ------->  com.st.
.smjd.dff    ------->  dff.smjd
.tst.dff     ------->  dff.tst


void *
ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
{
void        *value;
ngx_uint_t   i, n, key;
n = len;
//字符串尾部查找含有'.'的字符
while (n) {
if (name[n - 1] == '.') {
break;
}
n--;
}
//计算hash值
key = 0;
for (i = n; i < len; i++) {
key = ngx_hash(key, name[i]);
}

//在当前的hash中寻找hash,寻找范围为'.'后的
/*
如果name为test.com,那么这一层hash将寻找com
*/
value = ngx_hash_find(&hwc->hash, key, &name
, len - n);

//如果寻找存在
if (value) {
//如果value里面存放的是ngx_hash_wildcard_t
if ((uintptr_t) value & 2) {
//如果这个name是在前面的,相当于test.com的test
if (n == 0) {
//如果查找到的这个key是包含'.'的
if ((uintptr_t) value & 1) {
//返回NULL,交个上一层,让上一层返回自己的value
return NULL;
}
//如果这个key不包含'.',那么这就是我们要的key
//对内存标志进行恢复,并返回
hwc = (ngx_hash_wildcard_t *)((uintptr_t) value & (uintptr_t) ~3);
return hwc->value;
}
//如果该key还有前面的部分比如test.gen的test
//获取下一层通配符结构
hwc = (ngx_hash_wildcard_t *)((uintptr_t) value & (uintptr_t) ~3);
//继续在下一层的结构查找test。
value = ngx_hash_find_wc_head(hwc, name, n - 1);
//如果下一层返回不为NULL,就当成该key的value
if (value) {
return value;
}
/*否侧取这一层的value(如果这一层也为NULL,显而会继续往上层朔)*/
return hwc->value;
}

/*如果该key对应的value是最下层,也就是实际存放的就是value,且key后面一定是有'.'字符*/
if ((uintptr_t) value & 1) {
//如果这个name(key)是在前面的,相当于test.com的test
if (n == 0) {
//返回并上朔
return NULL;
}
//否则返回该value,已经到最下层,查找完毕
return (void *) ((uintptr_t) value & (uintptr_t) ~3);
}
/*如果该key对应的value是最下层,也就是实际存放的就是value,且key后面一定是没有有'.'字符,直接return*/
return value;
}
//如果寻找不存在,返回这一层key对应的value
return hwc->value;
}


这个逻辑比较绕,要多加思考,就能融会贯通。当然查找还有ngx_hash_find_wc_tail与其对应。实现大致一样。该函数查询包含通配符在末尾的key的hash表的,我们mail.xxx.*为例,请特别注意通配符在末尾的不像位于开始的通配符可以被省略掉。这样的通配符,可以匹配mail.xxx.com、mail.xxx.com.cn、mail.xxx.net之类的域名。

对于name中的key的初始化

test.com.*   ------->  test.com
te.com.*     ------->  te.com
st.com.*     ------->  st.com
smjd.dff.*   ------->  smjd.dff


void *
ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
{
void        *value;
ngx_uint_t   i, key;
key = 0;
//从前面开始查找test.*取test
for (i = 0; i < len; i++) {
if (name[i] == '.') {
break;
}
key = ngx_hash(key, name[i]);
}
//如果没有字符'.',也就是只剩最后的'*'字符,直接返回给上一层
if (i == len) {
return NULL;
}
//查找test
value = ngx_hash_find(&hwc->hash, key, name, i);

//如果test存在
if (value) {
/*
* the 2 low bits of value have the special meaning:
*     00 - value is data pointer;
*     11 - value is pointer to wildcard hash allowing "example.*".
*/
//如果对应的value是ngx_hash_wildcard_t
if ((uintptr_t) value & 2) {
i++;
//由于最后一个'*'没有省略,所以key后都是有'.'的
hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
//递归查找下一层
value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
//如果下一层查找成功
if (value) {
return value;
}
//如果下一层查找没有
return hwc->value;
}
/*如果是具体的value,已经没有下一层hash表了,存储的具体字符串,最后一个字符不是'.',所以后两位一定是00*/
return value;
}
//如果test不存在,则返回该层的value
return hwc->value;
}


最后我们来看一下ngx_hash_combined_t 的查找,结构如下

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


void *
ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name,
size_t len)
{
void  *value;
//在hash里面查找,如果有,就立即返回value
if (hash->hash.buckets) {
value = ngx_hash_find(&hash->hash, key, name, len);
if (value) {
return value;
}
}

if (len == 0) {
return NULL;
}

//head通配符查找。
if (hash->wc_head && hash->wc_head->hash.buckets) {
value = ngx_hash_find_wc_head(hash->wc_head, name, len);

if (value) {
return value;
}
}

//尾部通配符查找
if (hash->wc_tail && hash->wc_tail->hash.buckets) {
value = ngx_hash_find_wc_tail(hash->wc_tail, name, len);
if (value) {
return value;
}
}
return NULL;
}


总结

ngx_hash_wildcard_t可以说是对hash表的灵活应用,值得我们多看几遍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: