菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)
菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)
Author:Echo Chen(陈斌)
Email:chenb19870707@gmail.com
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 为 NULL19: 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_t36: 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"的指针
    
 
查找的思路就是依据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表的指针的时候,查询过程结束。
理解了这个,我们就能够看源码了,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.csdn.net/chen19870707
-
 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号