菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)

菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)

分类: Server - 菜鸟nginx源码剖析 10151人阅读 评论(1) 收藏 举报

目录(?)[+]

 


 

  • Author:Echo Chen(陈斌)

  • Email:chenb19870707@gmail.com

  • Blog:Blog.csdn.net/chen19870707

  • Date:Nov 3rd, 2014

    在前面一篇文章《菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)》 里介绍了 普通哈希表 和 带有通配符的哈希表 的基本结构和初始化方法,由于篇幅的原因未能解析完结,这篇继续解源码中剩余的部分。

  • 1.普通哈希表ngx_hash_t查找 ngx_hash_find

    普通哈希表的查找比较简单,思想就是先根据hash值找到对应桶,然后遍历这个桶的每一个元素,逐字匹配是否关键字完全相同,完全相同则找到,否则继续,直至找到这个桶的结尾(value = NULL)。

       1: /* @hash 表示哈希表的结构体
       2:  * @key  表示根据哈希方法计算出来的hash值
       3:  * @name 表示实际关键字地址
       4:  * @len  表示实际关键字长度
       5:  */
       6: void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
       7: {
       8:     ngx_uint_t       i;
       9:     ngx_hash_elt_t  *elt;
      10:  
      11:     //根据hash值找到桶索引
      12:     elt = hash->buckets[key % hash->size];
      13:  
      14:     if (elt == NULL) {
      15:         return NULL;
      16:     }
      17:  
      18:     //桶结束的标志为 value 为 NULL
      19:     while (elt->value) {
      20:         //关键字长度不匹配,下一个
      21:         if (len != (size_t) elt->len) {
      22:             goto next;
      23:         }
      24:         
      25:         //遍历关键字每一个字符,若全部对得上,则找到,否则有一个不同下一个
      26:         for (i = 0; i < len; i++) {
      27:             if (name[i] != elt->name[i]) {
      28:                 goto next;
      29:             }
      30:         }
      31:  
      32:         return elt->value;
      33:  
      34:     next:
      35:         //从elt关键字开始向后移动关键字长度个,并行对齐,即为下一个ngx_hash_elt_t
      36:         elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
      37:                                                sizeof(void *));
      38:         continue;
      39:     }
      40:  
      41:     return NULL;
      42: }

    2.支持通配符哈希表的前置通配符查找 ngx_hash_find_wc_head

     

    还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下图所示

    • 00 - value 是 "example.com" 和 "*.example.com"的数据指针
    • 01 - value 仅仅是 "*.example.com"的数据指针
    • 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指针
    • 11 - value 仅仅是 "*.example.com"的指针

      221627_1k2x_866208

    查找的思路就是根据value的指针信息来搜索,源代码如下:

     

       1: /* @hwc  表示支持通配符的哈希表的结构体
       2:  * @name 表示实际关键字地址
       3:  * @len  表示实际关键字长度
       4:  */
       5: void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
       6: {
       7:     void        *value;
       8:     ngx_uint_t   i, n, key;
       9:  
      10:     n = len;
      11:     
      12:     //从后往前搜索第一个dot,则n 到 len-1 即为关键字中最后一个 子关键字
      13:     while (n) {
      14:         if (name[n - 1] == '.') {
      15:             break;
      16:         }
      17:  
      18:         n--;
      19:     }
      20:  
      21:     key = 0;
      22:     
      23:     //n 到 len-1 即为关键字中最后一个 子关键字,计算其hash值
      24:     for (i = n; i < len; i++) {
      25:         key = ngx_hash(key, name[i]);
      26:     }
      27:     
      28:     //调用普通查找找到关键字的value(用户自定义数据指针)
      29:     value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);
      30:  
      31:     /**还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下:
      32:      * 00 - value 是 "example.com" 和 "*.example.com"的数据指针
      33:      * 01 - value 仅仅是 "*.example.com"的数据指针
      34:      * 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指针
      35:      * 11 - value 仅仅是 "*.example.com"的指针
      36:      */
      37:     if (value)
      38:     {
      39:  
      40:         if ((uintptr_t) value & 2) {
      41:             
      42:             //搜索到了最后一个子关键字且没有通配符,如"example.com"的example
      43:             if (n == 0) {
      44:                 //value低两位为11,仅为"*.example.com"的指针,这里没有通配符,没招到,返回NULL
      45:                 if ((uintptr_t) value & 1) {
      46:                     return NULL;
      47:                 }
      48:                 
      49:                 //value低两位为10,为"example.com"的指针,value就在下一级的ngx_hash_wildcard_t 的value中,去掉携带的低2位11
      50:                 hwc = (ngx_hash_wildcard_t *)
      51:                                           ((uintptr_t) value & (uintptr_t) ~3);
      52:                 return hwc->value;
      53:             }
      54:             
      55:             //还未搜索完,低两位为11或10,继续去下级ngx_hash_wildcard_t中搜索
      56:             hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
      57:             
      58:             //继续搜索 关键字中剩余部分,如"example.com",搜索 0 到 n -1 即为 example
      59:             value = ngx_hash_find_wc_head(hwc, name, n - 1);
      60:  
      61:             //若找到,则返回
      62:             if (value) 
      63:             {
      64:                 return value;
      65:             }
      66:             
      67:             //低两位为00 找到,即为wc->value
      68:             return hwc->value;
      69:         }
      70:  
      71:         //低两位为01
      72:         if ((uintptr_t) value & 1) 
      73:         {
      74:             //关键字没有通配符,错误返回空
      75:             if (n == 0) 
      76:             {
      77:                 return NULL;
      78:             }
      79:             
      80:             //有通配符,直接返回
      81:             return (void *) ((uintptr_t) value & (uintptr_t) ~3);
      82:         }
      83:  
      84:         //低两位为00,直接返回
      85:         return value;
      86:     }
      87:  
      88:     return hwc->value;
      89: }

     

    3.支持通配符哈希表的后置通配符查找 ngx_hash_find_wc_tail

     

    ngx_hash_find_wc_tail与前置通配符查找差不多,这里value低两位仅有两种标志,更加简单:

    • 00 - value 是指向 用户自定义数据
    • 11 - value的指向下一个哈希表
      源代码如下:
       1: void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
       2: {
       3:     void        *value;
       4:     ngx_uint_t   i, key;
       5:  
       6:  
       7:     key = 0;
       8:  
       9:     //从前往前搜索第一个dot,则0 到 i 即为关键字中第一个 子关键字
      10:     for (i = 0; i < len; i++)
      11:     {
      12:         if (name[i] == '.')
      13:         {
      14:             break;
      15:         }
      16:         //计算哈希值
      17:         key = ngx_hash(key, name[i]);
      18:     }
      19:  
      20:     //没有通配符,返回NULL
      21:     if (i == len)
      22:     {
      23:         return NULL;
      24:     }
      25:  
      26:     //调用普通查找找到关键字的value(用户自定义数据指针)
      27:     value = ngx_hash_find(&hwc->hash, key, name, i);
      28:  
      29:  
      30:     /**还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下:
      31:      * 00 - value 是数据指针
      32:      * 11 - value的指向下一个哈希表
      33:      */
      34:     if (value)
      35:     {
      36:         //低2位为11,value的指向下一个哈希表,递归搜索
      37:         if ((uintptr_t) value & 2)
      38:         {
      39:  
      40:             i++;
      41:  
      42:             hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
      43:  
      44:             value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
      45:  
      46:             //找到低两位00,返回
      47:             if (value) 
      48:             {
      49:                 return value;
      50:             }
      51:             
      52:             //找打低两位11,返回hwc->value
      53:             return hwc->value;
      54:         }
      55:  
      56:         return value;
      57:     }
      58:  
      59:     //低2位为00,直接返回数据
      60:     return hwc->value;
      61: }

     

     

    4.组合哈希表查找 ngx_hash_find_combined

    组合哈希表的查找思路非常简单,先在普通哈希表中查找,没找到再去前置通配符哈希表中查找,最后去后置通配符哈希表中查找,源代码如下:

       1: void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name,size_t len)
       2: {
       3:     void  *value;
       4:     
       5:     //在普通hash表中查找
       6:     if (hash->hash.buckets) {
       7:         value = ngx_hash_find(&hash->hash, key, name, len);
       8:  
       9:         if (value) {
      10:             return value;
      11:         }
      12:     }
      13:  
      14:     if (len == 0) {
      15:         return NULL;
      16:     }
      17:     
      18:     //在前置通配符哈希表中查找
      19:     if (hash->wc_head && hash->wc_head->hash.buckets) {
      20:         value = ngx_hash_find_wc_head(hash->wc_head, name, len);
      21:  
      22:         if (value) {
      23:             return value;
      24:         }
      25:     }
      26:     
      27:     //在后置通配符哈希表中查找
      28:     if (hash->wc_tail && hash->wc_tail->hash.buckets) {
      29:         value = ngx_hash_find_wc_tail(hash->wc_tail, name, len);
      30:  
      31:         if (value) {
      32:             return value;
      33:         }
      34:     }
      35:  
      36:     return NULL;
      37: }

     

     

    5.支持通配符哈希表初始化 ngx_hash_wildcard_init

    首先看一下ngx_hash_wildcard_init 的内存结构,当构造此类型的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表的指针的时候,查询过程结束。

     

    221627_1k2x_866208

     

    理解了这个,我们就可以看源代码了,ngx_hash_wildcard是一个递归函数,递归创建如上图的hash链表,如下为注释版源代码。

    精彩的读点有:

    • 由于指针都字节对齐了,低4位肯定为0,这种操作(name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2)) ) 巧妙的使用了指针的低位携带额外信息,节省了内存,让人不得不佩服ngx设计者的想象力。

       1: /*hinit为初始化结构体指针,names为预加入哈希表数组,elts为预加入数组大小
       2: 特别要注意的是这里的key已经都是被预处理过的。例如:“*.abc.com”或者“.abc.com”被预处理完成以后,
       3: 变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”*/
       4: ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,ngx_uint_t nelts)
       5: {
       6:     size_t                len, dot_len;
       7:     ngx_uint_t            i, n, dot;
       8:     ngx_array_t           curr_names, next_names;
       9:     ngx_hash_key_t       *name, *next_name;
      10:     ngx_hash_init_t       h;
      11:     ngx_hash_wildcard_t  *wdc;
      12:  
      13:     //初始化临时动态数组curr_names,curr_names是存放当前关键字的数组
      14:     if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
      15:                        sizeof(ngx_hash_key_t))
      16:         != NGX_OK)
      17:     {
      18:         return NGX_ERROR;
      19:     }
      20:  
      21:     //初始化临时动态数组next_names,next_names是存放关键字去掉后剩余关键字
      22:     if (ngx_array_init(&next_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK)
      23:     {
      24:         return NGX_ERROR;
      25:     }
      26:  
      27:     //遍历 names 数组
      28:     for (n = 0; n < nelts; n = i)
      29:     {
      30:         dot = 0;
      31:  
      32:         //查找 dot
      33:         for (len = 0; len < names[n].key.len; len++)
      34:         {
      35:             if (names[n].key.data[len] == '.')
      36:             {
      37:                 dot = 1;
      38:                 break;
      39:             }
      40:         }
      41:  
      42:         //将关键字dot以前的关键字放入curr_names
      43:         name = ngx_array_push(&curr_names);
      44:         if (name == NULL) {
      45:             return NGX_ERROR;
      46:         }
      47:  
      48:         name->key.len = len;
      49:         name->key.data = names[n].key.data;
      50:         name->key_hash = hinit->key(name->key.data, name->key.len);
      51:         name->value = names[n].value;
      52:  
      53:         dot_len = len + 1;
      54:  
      55:         //len指向dot后剩余关键字
      56:         if (dot)
      57:         {
      58:             len++;
      59:         }
      60:  
      61:         next_names.nelts = 0;
      62:  
      63:         //如果names[n] dot后还有剩余关键字,将剩余关键字放入next_names中
      64:         if (names[n].key.len != len)
      65:         {
      66:             next_name = ngx_array_push(&next_names);
      67:             if (next_name == NULL) {
      68:                 return NGX_ERROR;
      69:             }
      70:  
      71:             next_name->key.len = names[n].key.len - len;
      72:             next_name->key.data = names[n].key.data + len;
      73:             next_name->key_hash = 0;
      74:             next_name->value = names[n].value;
      75:  
      76:         }
      77:  
      78:         //如果上面搜索到的关键字没有dot,从n+1遍历names,将关键字比它长的全部放入next_name
      79:         for (i = n + 1; i < nelts; i++)
      80:         {
      81:             //前len个关键字相同
      82:             if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0) {
      83:                 break;
      84:             }
      85:  
      86:  
      87:             if (!dot
      88:                 && names[i].key.len > len
      89:                 && names[i].key.data[len] != '.')
      90:             {
      91:                 break;
      92:             }
      93:  
      94:             next_name = ngx_array_push(&next_names);
      95:             if (next_name == NULL) {
      96:                 return NGX_ERROR;
      97:             }
      98:  
      99:             next_name->key.len = names[i].key.len - dot_len;
     100:             next_name->key.data = names[i].key.data + dot_len;
     101:             next_name->key_hash = 0;
     102:             next_name->value = names[i].value;
     103:  
     104:         }
     105:  
     106:         //如果next_name非空
     107:         if (next_names.nelts)
     108:         {
     109:             h = *hinit;
     110:             h.hash = NULL;
     111:  
     112:             //递归,创建一个新的哈西表
     113:             if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,next_names.nelts) != NGX_OK)
     114:             {
     115:                 return NGX_ERROR;
     116:             }
     117:  
     118:             wdc = (ngx_hash_wildcard_t *) h.hash;
     119:             
     120:             //如上图,将用户value值放入新的hash表
     121:             if (names[n].key.len == len)
     122:             {
     123:                 wdc->value = names[n].value;
     124:             }
     125:             
     126:             //并将当前value值指向新的hash表
     127:             name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
     128:  
     129:         } else if (dot)
     130:         {
     131:             name->value = (void *) ((uintptr_t) name->value | 1);
     132:         }
     133:     }
     134:  
     135:     //将最外层hash初始化
     136:     if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts,curr_names.nelts) != NGX_OK)
     137:     {
     138:         return NGX_ERROR;
     139:     }
     140:  
     141:     return NGX_OK;
     142: }

     

    6. 哈希键数组初始化 ngx_hash_keys_array_init

     

    初始化ngx_hash_keys_arrays_t 结构体,type的取值范围只有两个,NGX_HASH_SMALL表示初始化元素较少,NGX_HASH_LARGE表示初始化元素较多,在向ha中加入时必须调用此方法

       1: ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
       2: {
       3:     ngx_uint_t  asize;
       4:  
       5:     if (type == NGX_HASH_SMALL)
       6:     {
       7:         asize = 4;
       8:         ha->hsize = 107;
       9:     }
      10:     else
      11:     {
      12:         asize = NGX_HASH_LARGE_ASIZE;
      13:         ha->hsize = NGX_HASH_LARGE_HSIZE;
      14:     }
      15:  
      16:     //初始化 存放非通配符关键字的数组
      17:     if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
      18:     {
      19:         return NGX_ERROR;
      20:     }
      21:  
      22:     //初始化 存放前置通配符处理好的关键字 数组
      23:     if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
      24:     {
      25:         return NGX_ERROR;
      26:     }
      27:  
      28:     //初始化 存放后置通配符处理好的关键字 数组
      29:     if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))!= NGX_OK)
      30:     {
      31:         return NGX_ERROR;
      32:     }
      33:  
      34:     /*初始化 二位数组 ,这个数组存放的第一个维度代表的是bucket的编号,
      35:       那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
      36:       假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
      37:       那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
      38:       该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
      39:     ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
      40:     if (ha->keys_hash == NULL)
      41:     {
      42:         return NGX_ERROR;
      43:     }
      44:  
      45:     // 该数组在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
      46:     ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,sizeof(ngx_array_t) * ha->hsize);
      47:  
      48:     if (ha->dns_wc_head_hash == NULL)
      49:     {
      50:         return NGX_ERROR;
      51:     }
      52:  
      53:     // 该数组在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
      54:     ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
      55:     if (ha->dns_wc_tail_hash == NULL)
      56:     {
      57:         return NGX_ERROR;
      58:     }
      59:  
      60:     return NGX_OK;
      61: }

     

    7. 向ngx_hash_keys_array中添加关键字

     

       1: ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
       2: {
       3:     ngx_uint_t  asize;
       4:  
       5:     if (type == NGX_HASH_SMALL)
       6:     {
       7:         asize = 4;
       8:         ha->hsize = 107;
       9:     }
      10:     else
      11:     {
      12:         asize = NGX_HASH_LARGE_ASIZE;
      13:         ha->hsize = NGX_HASH_LARGE_HSIZE;
      14:     }
      15:  
      16:     //初始化 存放非通配符关键字的数组
      17:     if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
      18:     {
      19:         return NGX_ERROR;
      20:     }
      21:  
      22:     //初始化 存放前置通配符处理好的关键字 数组
      23:     if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
      24:     {
      25:         return NGX_ERROR;
      26:     }
      27:  
      28:     //初始化 存放后置通配符处理好的关键字 数组
      29:     if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))!= NGX_OK)
      30:     {
      31:         return NGX_ERROR;
      32:     }
      33:  
      34:     /*初始化 二位数组 ,这个数组存放的第一个维度代表的是bucket的编号,
      35:       那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
      36:       假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
      37:       那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
      38:       该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
      39:     ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
      40:     if (ha->keys_hash == NULL)
      41:     {
      42:         return NGX_ERROR;
      43:     }
      44:  
      45:     // 该数组在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
      46:     ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,sizeof(ngx_array_t) * ha->hsize);
      47:  
      48:     if (ha->dns_wc_head_hash == NULL)
      49:     {
      50:         return NGX_ERROR;
      51:     }
      52:  
      53:     // 该数组在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
      54:     ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
      55:     if (ha->dns_wc_tail_hash == NULL)
      56:     {
      57:         return NGX_ERROR;
      58:     }
      59:  
      60:     return NGX_OK;
      61: }

    -

    Echo Chen:Blog.csdn.net/chen19870707

    -

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值