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

时间:2024-07-03 18:04:02

 

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

 

  • Author:Echo Chen(陈斌)

  • Email:chenb19870707@gmail.com

  • Blog:Blog.****.net/chen19870707

  • Date:Nov 3rd, 2014

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

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

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

       1: /* @hash 表示哈希表的结构体

       2:  * @key  表示依据哈希方法计算出来的hash值

       3:  * @name 表示实际keyword地址

       4:  * @len  表示实际keyword长度

       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:         //keyword长度不匹配,下一个

      21:         if (len != (size_t) elt->len) {

      22:             goto next;

      23:         }

      24:         

      25:         //遍历keyword每个字符,若所有对得上,则找到,否则有一个不同下一个

      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:         //从eltkeyword開始向后移动keyword长度个。并行对齐,即为下一个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"的指针

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

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

     

       1: /* @hwc  表示支持通配符的哈希表的结构体

       2:  * @name 表示实际keyword地址

       3:  * @len  表示实际keyword长度

       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 即为keyword中最后一个 子keyword

      13:     while (n) {

      14:         if (name[n - 1] == '.') {

      15:             break;

      16:         }

      17:  

      18:         n--;

      19:     }

      20:  

      21:     key = 0;

      22:     

      23:     //n 到 len-1 即为keyword中最后一个 子keyword,计算其hash值

      24:     for (i = n; i < len; i++) {

      25:         key = ngx_hash(key, name[i]);

      26:     }

      27:     

      28:     //调用普通查找找到keyword的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:             //搜索到了最后一个子keyword且没有通配符,如"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:             //继续搜索 keyword中剩余部分,如"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:             //keyword没有通配符,错误返回空

      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 即为keyword中第一个 子keyword

      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:     //调用普通查找找到keyword的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

    组合哈希表的查找思路很easy,先在普通哈希表中查找。没找到再去前置通配符哈希表中查找,最后去后置通配符哈希表中查找。源码例如以下:

       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表的指针的时候,查询过程结束。

     

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

     

    理解了这个,我们就能够看源码了,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是存放当前keyword的数组
      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是存放keyword去掉后剩余keyword
      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:         //将keyworddot曾经的keyword放入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后剩余keyword
      56:         if (dot)
      57:         {
      58:             len++;
      59:         }
      60:  
      61:         next_names.nelts = 0;
      62:  
      63:         //假设names[n] dot后还有剩余keyword。将剩余keyword放入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:         //假设上面搜索到的keyword没有dot,从n+1遍历names,将keyword比它长的所有放入next_name
      79:         for (i = n + 1; i < nelts; i++)
      80:         {
      81:             //前len个keyword同样
      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:     //初始化 存放非通配符keyword的数组

      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:     //初始化 存放前置通配符处理好的keyword 数组

      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:     //初始化 存放后置通配符处理好的keyword 数组

      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中加入keyword

     

       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:     //初始化 存放非通配符keyword的数组

      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:     //初始化 存放前置通配符处理好的keyword 数组

      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:     //初始化 存放后置通配符处理好的keyword 数组

      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.****.net/chen19870707

    -