nginx源码分析2———基础数据结构六(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提供给了此辅助类型。有了这个就可以清晰了解上一篇博文

源码分析

相关结构

ngx_hash_keys_arrays_t就是我们说的辅助结构,辅助我们更容易去构建ngx_hash_wildcard_t

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表的桶的个数
pool 构建hash表使用的pool
temp_pool在构建这个类型以及最终的三个hash表过程中可能用到临时pool
keys存放所有非通配符key的数组
keys_hash这是个二维数组,第一个维度代表的是bucket的编号,那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key
dns_wc_head放前向通配符key被处理完成以后的值
dns_wc_tail存放后向通配符key被处理完成以后的值
dns_wc_head_hash该值在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复
dns_wc_tail_hash该值在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复

结构的初始化
ngx_int_t
ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
{
    ngx_uint_t  asize;
    //根据type来选择是大号桶的个数还是小号桶的个数
    if (type == NGX_HASH_SMALL) {
        asize = 4;
        ha->hsize = 107;
    } else {
        asize = NGX_HASH_LARGE_ASIZE;
        ha->hsize = NGX_HASH_LARGE_HSIZE;
    }

    //初始化非通配符的keys数组,数组容量为asize
    if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
    //初始化头部有通配符的keys数组,数组容量为asize
    if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize,sizeof(ngx_hash_key_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
    //初始化尾部有通配符的keys数组,数组容量为asize
    if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
    //初始化二维数组keys_hash
    ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
    if (ha->keys_hash == NULL) {
        return NGX_ERROR;
    }
    //初始化头部通配符冲突检测
    ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
    if (ha->dns_wc_head_hash == NULL) {
        return NGX_ERROR;
    }
    //初始化尾部通配符冲突检测
    ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
    if (ha->dns_wc_tail_hash == NULL) {
        return NGX_ERROR;
    }
    return NGX_OK;
}

ngx_hash_keys_array_init初始化操作。都是一系列分配内存操作。下面我们来看一看ngx_hash_add_key操作。

ngx_int_t
ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,ngx_uint_t flags)
{
    size_t           len;
    u_char          *p;
    ngx_str_t       *name;
    ngx_uint_t       i, k, n, skip, last;
    ngx_array_t     *keys, *hwc;
    ngx_hash_key_t  *hk;

    last = key->len;

    if (flags & NGX_HASH_WILDCARD_KEY) {
        /*
         * supported wildcards:
         *     "*.example.com", ".example.com", and "www.example.*"
         */

        n = 0;

        for (i = 0; i < key->len; i++) {
            //如果含有两个*,就return不处理
            if (key->data[i] == '*') {
                if (++n > 1) {
                    return NGX_DECLINED;
                }
            }

            //如果有挨着的两个点也不处理
            if (key->data[i] == '.' && key->data[i + 1] == '.') {
                return NGX_DECLINED;
            }
        }
        //skip是前省略的,last为后面省略的      
        //如果是'.example.com'
        if (key->len > 1 && key->data[0] == '.') {
            skip = 1;
            goto wildcard;
        }
        if (key->len > 2) 
        {
            //如果是'*.example.com'
            if (key->data[0] == '*' && key->data[1] == '.') 
            {
                skip = 2;
                goto wildcard;
            }
            //'www.example.*'
            if (key->data[i - 2] == '.' && key->data[i - 1] == '*')
             {
                skip = 0;
                last -= 2;
                goto wildcard;
            }
        }

        //如果含有*就不能当做exact hash处理了
        if (n) {
            return NGX_DECLINED;
        }
    }

    /* exact hash */
    //处理精确hash查找的key
    k = 0;

    //计算hashcode
    for (i = 0; i < last; i++) {
        if (!(flags & NGX_HASH_READONLY_KEY)) {
            key->data[i] = ngx_tolower(key->data[i]);
        }
        k = ngx_hash(k, key->data[i]);
    }

    k %= ha->hsize;

    /* check conflicts in exact hash */

    name = ha->keys_hash[k].elts;
    //查看是否冲突
    if (name) {
        for (i = 0; i < ha->keys_hash[k].nelts; i++) 
        {
            if (last != name[i].len) {
                continue;
            }

            if (ngx_strncmp(key->data, name[i].data, last) == 0)
            {
                return NGX_BUSY;
            }
        }
    } else {
        if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
                           sizeof(ngx_str_t))
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    }

    name = ngx_array_push(&ha->keys_hash[k]);
    if (name == NULL) {
        return NGX_ERROR;
    }

    *name = *key;

    hk = ngx_array_push(&ha->keys);
    if (hk == NULL) {
        return NGX_ERROR;
    }

    hk->key = *key;
    hk->key_hash = ngx_hash_key(key->data, last);
    hk->value = value;

    return NGX_OK;


wildcard:

    /* wildcard hash */
    //根据skip和last截断后计算hashcode
    k = ngx_hash_strlow(&key->data[skip], &key->data[skip], last - skip);
    k %= ha->hsize;
    //处理'.example.com'
    if (skip == 1) {
        /* check conflicts in exact hash for ".example.com" */

        name = ha->keys_hash[k].elts;
        //在key_hash中寻找,看是否有重复
        if (name) {
            //长度为去掉头与尾
            len = last - skip;
            for (i = 0; i < ha->keys_hash[k].nelts; i++) 
            {
                if (len != name[i].len) {
                    continue;
                }

                if (ngx_strncmp(&key->data[1], name[i].data, len) == 0) 
                {
                    return NGX_BUSY;
                }
            }
        } else {
        //没有救初始化数组,将该元素放进去
            if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,sizeof(ngx_str_t)) != NGX_OK)
            {
                return NGX_ERROR;
            }
        }

        name = ngx_array_push(&ha->keys_hash[k]);
        if (name == NULL) {
            return NGX_ERROR;
        }
        //长度为last减1  只放入点前面的
        name->len = last - 1;
        name->data = ngx_pnalloc(ha->temp_pool, name->len);
        if (name->data == NULL) {
            return NGX_ERROR;
        }
        //拷贝过去。算是深拷贝
        ngx_memcpy(name->data, &key->data[1], name->len);
    }

    if (skip) {

        /*
         * convert "*.example.com" to "com.example.\0"
         *      and ".example.com" to "com.example\0"
         */
        //对 *.example.com与.example.com做反转处理
        p = ngx_pnalloc(ha->temp_pool, last);
        if (p == NULL) {
            return NGX_ERROR;
        }
        重置n与len
        len = 0;
        n = 0;

        //反转  将*.example.com处理为.example.com
        //     将.example.com处理为.com
        for (i = last - 1; i; i--) {
            if (key->data[i] == '.') {
                ngx_memcpy(&p[n], &key->data[i + 1], len);
                n += len;
                p[n++] = '.';
                len = 0;
                continue;
            }
            len++;
        }
        //     将.com处理为example.com
        if (len) {
            ngx_memcpy(&p[n], &key->data[1], len);
            n += len;
        }

        //p是普通字符串,在最后补0
        p[n] = '\0';

        hwc = &ha->dns_wc_head;
        keys = &ha->dns_wc_head_hash[k];
    } else {
        /* convert "www.example.*" to "www.example\0" */

        last++;
        //处理后置通配符
        p = ngx_pnalloc(ha->temp_pool, last);
        if (p == NULL) {
            return NGX_ERROR;
        }
        //直接拷贝www.example.到p,由于last的长度限制
        //会把最后一个.变成0(详见ngx_cpystrn函数)
        ngx_cpystrn(p, key->data, last);
        //最后p为'www.example\0'

        hwc = &ha->dns_wc_tail;
        keys = &ha->dns_wc_tail_hash[k];
    }

    /* check conflicts in wildcard hash */
    //处理是否有冲突
    name = keys->elts;

    if (name) {
        //注意后面有通配符的的last有+1,所以len会以.结尾
        //前置通配符会去掉skip的字符
        len = last - skip;
        //查看是否有重复
        for (i = 0; i < keys->nelts; i++) {
            if (len != name[i].len) {
                continue;
            }

            if (ngx_strncmp(key->data + skip, name[i].data, len) == 0) {
                return NGX_BUSY;
            }
        }
    } else {
        if (ngx_array_init(keys, ha->temp_pool, 4, sizeof(ngx_str_t)) != NGX_OK)
        {
            return NGX_ERROR;
        }
    }
    //没有重复就放进去
    name = ngx_array_push(keys);
    if (name == NULL) {
        return NGX_ERROR;
    }
    //长度和刚才比较所用的长度相同
    name->len = last - skip;
    name->data = ngx_pnalloc(ha->temp_pool, name->len);
    if (name->data == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(name->data, key->data + skip, name->len);


    /* add to wildcard hash */
    //假如hash keys里面去,以便初始化通配符字符串
    hk = ngx_array_push(hwc);
    if (hk == NULL) {
        return NGX_ERROR;
    }
    //注意长度为last - 1
    //也就是前置会减去一个字符
    //后置减去两个
    hk->key.len = last - 1;
    hk->key.data = p;
    hk->key_hash = 0;
    hk->value = value;

    return NGX_OK;
}

最后说一下本文的例子

*.test.com    -------->  com.test.
.test.com     -------->  com.test
yang.test.*   -------->  yang.test

总结

把本节与上一节联系起来,就会感觉特别清晰。本节中的key都主要用于初始化,和以后的查找通配符hash表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值