【nginx源码学习与运用】系列博客中的示例代码在csdn的代码托管服务器CODE上,地址https://code.csdn.net/u012819339/nginx_study ,你可以将其自由的下载到本地,或者通过Git来实时获取更新
nginx的哈希表结构在代码中比较常用,nginx实现了支持通配符的哈希表,主要用来处理域名匹配,本篇博客将为鞋童们展示怎么使用这nginx的哈希表结构。(哈希也即散列的意思)
arvik其实还是有些疑惑的,不知道nginx为什么要将哈希表结构设计的如此复杂,并且构造散列表之前要先把数据加到ngx_hash_keys_array_t结构中,然后再调用ngx_hash_init或ngx_hash_wildcard_init来建立真正的散列表。看来还是没有理解精髓啊
相关结构
//哈希表槽
typedef struct {
void *value; //自定义数据指针
u_short len; //元素关键字长度
u_char name[1]; //元素关键字首地址
} ngx_hash_elt_t;
//哈希表
typedef struct {
ngx_hash_elt_t **buckets; //指向散列表的首地址,也是第一个槽的地址
ngx_uint_t size; //散列表中槽的总数
} ngx_hash_t;
//前置或后置通配符的散列表
typedef struct {
ngx_hash_t hash; //基本散列表
void *value; //指向用户数据
} ngx_hash_wildcard_t;
typedef struct {
ngx_str_t key; //元素关键字
ngx_uint_t key_hash; //散列后的关键码
void *value; //指向实际的用户数据
} ngx_hash_key_t;
//哈希方法
typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);
//支持简单通配符的散列表,适合做域名匹配
typedef struct {
ngx_hash_t hash; //用于精确匹配的散列表列表
ngx_hash_wildcard_t *wc_head; //用于查询前置匹配通配符的散列表
ngx_hash_wildcard_t *wc_tail; //用于查询后置匹配通配符的散列表
} ngx_hash_combined_t;
//用于初始化散列表
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; //内存池,它分配散列表(最多3个,包括一个普通散列表,一个前置和一个后置通配符散列表)中的所有槽
ngx_pool_t *temp_pool; //临时内存池,它仅存在于初始化散列表之前,它主要用于分配一些临时的动态数组,带通配符的元素在初始化时需要用到这些数组
} ngx_hash_init_t;
结构图
ngx_hash_combined_t
通配符列表结构:
ngx_hash_keys_arrays_t
中动态数组、散列表成员:
操作方法
两种散列方法:
函数 | 解释 |
---|---|
ngx_hash_key | 使用BKDR将任意长度的字符串映射为整形 |
ngx_hash_key_lc | 将字符串全部小写后,使用BKDR将任意长度的字符串映射为整形 |
ngx_hash_wildcard_t提供的方法
函数 | 解释 |
---|---|
ngx_hash_find_wc_head | 将待查询关键字name转换为前置散列规则下的字符串再递归查询,成功则返回找到元素所指向的用户数据,失败返回NULL |
ngx_hash_find_wc_tail | 将待查询关键字name转换为后置散列规则下的字符串再递归查询,成功则返回找到元素所指向的用户数据,失败返回NULL |
ngx_hash_init_t提供的两个初始化方法
函数 | 解释 |
---|---|
ngx_hash_init | hinit是散列表初始化结构体指针,names是数组首地址,nelts是names数组的元素数目。数组元素以ngx_hash_key_t作为结构体,它存储着预添加到散列表中的元素。成功则返回NGX_OK,失败返回NGX_ERROR |
ngx_hash_wildcard_init | 基本同上,初始化通配符散列表 |
ngx_hash_key_arrays_t提供的两个方法
函数 | 解释 |
---|---|
ngx_hash_keys_array_init | ha是要初始化的ngx_hash_keys_arrays_t结构体指针,type两个取值:NGX_HASH_SMALL表示待初始化的元素较少,NGX_HASH_LARGE表示待初始化的元素较多,成功则返回NGX_OK,失败返回NGX_ERROR |
ngx_hash_add_key | 想ha中添加1个元素,成功则返回NGX_OK,失败返回NGX_ERROR,type三种取值:NGX_HASH_WILDCARD_KEY表示需要处理通配符;NGX_HASH_READONLY_KEY表示关键字不可做更改,即不可以通过全小写关键字来获取散列码;其他值表示不处理通配符,允许通过把关键字全小写来获取散列码 |
示例代码
arvik将nginx中的部分基础结构代码提出来了,好作为新手学习练习使用。见 https://code.csdn.net/u012819339/nginx_study
带通配符散列表的使用例子main.c
/*
blog: http://blog.csdn.net/u012819339
email: [email protected]
author: arvik
*/
#include <stdio.h>
#include <string.h>
#include "ak_core.h"
#include "pt.h"
typedef struct twhash
{
ngx_str_t name;
char seq;
}TWHash;
int main()
{
ngx_pool_t *p;
ngx_hash_init_t hash;
ngx_hash_keys_arrays_t ha;
ngx_hash_combined_t combinedHash; //支持通配符的散列表
int i =0;
p = ngx_create_pool(NGX_DEFAULT_POOL_SIZE); //16KB
if(p == NULL)
return -1;
ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE);
if(ha.temp_pool == NULL)
return -2;
ha.pool = p;
if(ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK)
{
return -3;
}
TWHash testnode[3];
//这里不能用常量区的字符串来初始化ngx_str变量,应该申请内存来存放所需的字符串,因为ngx_hash_add_key方法可能会将字符串的内容改变(大写变小写)
testnode[0].name.data = ngx_pcalloc(p, ngx_strlen("*.test.com"));
testnode[0].name.len = strlen("*.test.com");
ngx_memcpy(testnode[0].name.data, "*.test.com", testnode[0].name.len);
testnode[1].name.data = ngx_pcalloc(p, ngx_strlen("www.test.*"));
testnode[1].name.len = strlen("www.test.*");
ngx_memcpy(testnode[1].name.data, "www.test.*", testnode[1].name.len);
testnode[2].name.data = ngx_pcalloc(p, ngx_strlen("www.test.com"));
testnode[2].name.len = strlen("www.test.com");
ngx_memcpy(testnode[2].name.data, "www.test.com", testnode[2].name.len);
//添加到ha中
for(i = 0; i<3; i++)
{
testnode[i].seq = i;
ngx_hash_add_key(&ha, &testnode[i].name, &testnode[i], NGX_HASH_WILDCARD_KEY); //NGX_HASH_WILDCARD_KEY表明可以处理带通配符的关键字
}
hash.key = ngx_hash_key_lc;
hash.max_size = 100;
hash.bucket_size = 48;
hash.name = "test_server_name_hash";
hash.pool = p;
//初始化精确匹配散列表
if(ha.keys.nelts)
{
hash.hash = &combinedHash.hash;
hash.temp_pool = NULL;
if(ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK)
return -1;
}
//初始化前置通配符散列表
if(ha.dns_wc_head.nelts)
{
hash.hash = NULL;
//ngx_hash_wildcard_init方法需要使用临时内存池
hash.temp_pool = ha.temp_pool;
if(ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, ha.dns_wc_head.nelts) != NGX_OK)
{
return -1;
}
combinedHash.wc_head = (ngx_hash_wildcard_t *)hash.hash;
}
//初始化后置通配符散列表
if(ha.dns_wc_tail.nelts)
{
hash.hash = NULL;
//ngx_hash_wildcard_init方法需要使用临时内存池
hash.temp_pool = ha.temp_pool;
if(ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, ha.dns_wc_tail.nelts) != NGX_OK)
{
return -1;
}
combinedHash.wc_tail = (ngx_hash_wildcard_t *)hash.hash;
}
//此时临时内存池已经没有存在的意义了
ngx_destroy_pool(ha.temp_pool);
//查询www.test.org,它应该匹配www.test.*
ngx_str_t mystr = ngx_string("www.test.org");
TWHash *mytesth = ngx_hash_find_combined(&combinedHash, ngx_hash_key_lc(mystr.data, mystr.len), mystr.data, mystr.len);
if(mytesth == NULL)
{
PT_Warn("not found!\n");
}
else
{
PT_Info("node mytest name:%*s\n", mytesth->name.len, mytesth->name.data);
}
//查询www.test.com,它匹配 www.test.* *.test.com www.test.com 这三项,但是nginx会返回那一项呢?
ngx_str_t mystr2 = ngx_string("www.test.com");
TWHash *mytesth2 = ngx_hash_find_combined(&combinedHash, ngx_hash_key_lc(mystr2.data, mystr2.len), mystr2.data, mystr2.len);
if(mytesth2 == NULL)
{
PT_Warn("not found!\n");
}
else
{
PT_Info("node mytest2 name:%.*s\n", mytesth2->name.len, mytesth2->name.data);
}
ngx_destroy_pool(p);
return 0;
}
运行结果
截图如下: