在上一篇博文介绍了nginx中基本hash表的实现,今天主要是来介绍nginx是如何实现支持通配符的hash表。话说在看支持通配符的hash表源码时我惊奇地发现它的设计思路居然和我之前设计的中文字典树基本一致,觉得nginx的设计也不过如此,但是看完hash表源码后我才发现我还是too young too simple,nginx的hash表考虑到的问题我想的要多得多!
二、支持通配符的hash表
nginx为了处理带有通配符的域名字符串的匹配问题,实现了支持通配符的hash表nginx中的通配符有两种形式:一种是通配符在前面的例如“*.test.com”,这样可以省略"*"号,写成“.test.com”;还有一种是通配符在后面的例如"www.test.*",这样域名不能省略“*”号,因为这样的通配符可以匹配“www.test.com”、“www.test.cn”、“www.test.org”等域名。注意:nginx不能同时包含在前和在后的通配符例如“*.test.*”。
而这个支持通配符的hash表ngx_hash_wildcard_t实际上是对基本hash表的一层封装(后面会讲到)。
在查找的字符串时,先是到基本hash表中查找元素,如果没有找到就去前置通配符的hash表中查找,如果还是没找到就去后置通配符的hash表中查找。
(1)支持通配符hash表的数据结构
//带通配符的hash表结构
typedef struct
{
ngx_hash_t hash;// 基本散列表
void *value;//指向真正的value或NULL
} ngx_hash_wildcard_t;
//组合类型哈希表
typedef struct
{
ngx_hash_t hash;//基本hash表
ngx_hash_wildcard_t *wc_head;//前置通配符hash表
ngx_hash_wildcard_t *wc_tail;//后置通配符hash表
} ngx_hash_combined_t;
(2)带通配符hash表的内存布局图
nginx中通过多级hash实现了通配符的hash表,如下图所示:
(3)支持通配符hash表的初始化
nginx在初始化通配符的hash表时,实际上将key字符串以“.”为分隔符拆分成多个key字段。将所有key字符串的第一个key字段去重后存到一个基本hash表中。再将所有有相同的第一个key字段的字符串(可能有好几组)去掉第一个key字段后分别递归处理。
总的流程如下:为临时数组申请空间->遍历数组中元素找出当前字符串的第一个key字段并放入curr_names数组->将具有相同key字段的key字符串的剩余部分放入next_names数组->递归处理next_names数组->回到第二步,如果已经遍历完则将curr_names数组元素放入hash表。
1、为临时数组申请空间
由于是把所有key字符串的第一个key字段提取出来,那么不重复的key字段一定不会超过nelts,同样不重复的去掉key字符串剩余部分字符串个数也不会超过nelts个。
//为临时数组curr_names申请空间,用于存放当前节点的key字段数组
if (ngx_array_init(&curr_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK)
{
return NGX_ERROR;
}
//为临时数组next_names申请空间,用于存放除去当前key字段后剩余字符串数组
if (ngx_array_init(&next_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK)
{
return NGX_ERROR;
}