【nginx源码学习与运用 八】哈希表结构ngx_hash_t

【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;
}

运行结果

截图如下:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/u012819339/article/details/53607274