7.7.3 带通配符散列表的使用例子

散列表元素ngx_hash_elt_t中value指针指向的数据结构为下面定义的TestWildcardHash-Node结构体,代码如下。


typedef struct{

//用于散列表中的关键字

ngx_str_t servername;

//这个成员仅是为了方便区别而已

ngx_int_t seq;

}TestWildcardHashNode;


每个散列表元素的关键字是servername字符串。下面先定义ngx_hash_init_t和ngx_hash_keys_arrays_t变量,为初始化散列表做准备,代码如下。


//定义用于初始化散列表的结构体

ngx_hash_init_t hash;

/ngx_hash_keys_arrays_t用于预先向散列表中添加元素,这里的元素支持带通配符/

ngx_hash_keys_arrays_t ha;

//支持通配符的散列表

ngx_hash_combined_t combinedHash;

ngx_memzero(&ha,sizeof(ngx_hash_keys_arrays_t));


combinedHash是我们定义的用于指向散列表的变量,它包括指向3个散列表的指针,下面会依次给这3个散列表指针赋值。


//临时内存池只是用于初始化通配符散列表,在初始化完成后就可以销毁掉

ha.temp_pool=ngx_create_pool(16384,cf->log);

if(ha.temp_pool==NULL){

return NGX_ERROR;

}


/*由于这个例子是在ngx_http_mytest_postconf函数中的,所以就用了ngx_conf_t类型的cf下的内存池作为散列表的内存池


ha.pool=cf->pool;


调用ngx_hash_keys_array_init方法来初始化ha,为下一步向ha中加入散列表元素做好准备,代码如下。


if(ngx_hash_keys_array_init(&ha,NGX_HASH_LARGE)!=NGX_OK){

return NGX_ERROR;

}


本节按照图7-12和图7-15中的例子建立3个数据,并且会覆盖7.7节中介绍的散列表内容。我们建立的testHashNode[3]这3个TestWildcardHashNode类型的结构体,分别表示可以用前置通配符匹配的散列表元素、可以用后置通配符匹配的散列表元素、需要完全匹配的散列表元素。


TestWildcardHashNode testHashNode[3];

testHashNode[0].servername.len=ngx_strlen("*.test.com");

testHashNode[0].servername.data=ngx_pcalloc(cf->pool,ngx_strlen("*.test.com"));

ngx_memcpy(testHashNode[0].servername.data,".test.com",ngx_strlen(".test.com"));

testHashNode[1].servername.len=ngx_strlen("www.test.*");

testHashNode[1].servername.data=ngx_pcalloc(cf->pool,ngx_strlen("www.test.*"));

ngx_memcpy(testHashNode[1].servername.data,"www.test.",ngx_strlen("www.test."));

testHashNode[2].servername.len=ngx_strlen("www.test.com");

testHashNode[2].servername.data=ngx_pcalloc(cf->pool,ngx_strlen("www.test.com"));

ngx_memcpy(testHashNode[2].servername.data,"www.test.com",ngx_strlen("www.test.com"));


下面通过调用ngx_hash_add_key方法将testHashNode[3]这3个成员添加到ha中。


for(i=0;i<3;i++)

{

testHashNode[i].seq=i;

ngx_hash_add_key(&ha,&testHashNode[i].servername,

&testHashNode[i],NGX_HASH_WILDCARD_KEY);

}


注意,在上面添加散列表元素时,flag设置为NGX_HASH_WILDCARD_KEY,这样才会处理带通配符的关键字。

在调用ngx_hash_init_t的初始化函数前,先得设置好ngx_hash_init_t中的成员,如槽的大小、散列方法等,如下所示。


hash.key=ngx_hash_key_lc;

hash.max_size=100;

hash.bucket_size=48;

hash.name="test_server_name_hash";

hash.pool=cf->pool;


ha的keys动态数组中存放的是需要完全匹配的关键字,如果keys数组不为空,那么开始初始化第1个散列表,代码如下。


if(ha.keys.nelts){

/需要显式地把ngx_hash_init_t中的hash指针指向combinedHash中的完全匹配散列表/

hash.hash=&combinedHash.hash;

//初始化完全匹配散列表时不会使用到临时内存池

hash.temp_pool=NULL;

/将keys动态数组直接传给ngx_hash_init方法即可,ngx_hash_init_t中的hash指针就是初始化成功的散列表/

if(ngx_hash_init(&hash,ha.keys.elts,ha.keys.nelts)!=NGX_OK)

{

return NGX_ERROR;

}

}

下面继续初始化前置通配符散列表,代码如下。

if(ha.dns_wc_head.nelts){

hash.hash=NULL;

//注意,ngx_hash_wildcard_init方法需要使用临时内存池

hash.temp_pool=ha.temp_pool;

if(ngx_hash_wildcard_init(&hash,ha.dns_wc_head.elts,

ha.dns_wc_head.nelts)!=NGX_OK)

{

return NGX_ERROR;

}

/ngx_hash_init_t中的hash指针是ngx_hash_wildcard_init初始化成功的散列表,需要将它赋到combinedHash.wc_head前置通配符散列表指针中/

combinedHash.wc_head=(ngx_hash_wildcard_t*)hash.hash;

}

下面继续初始化后置通配符散列表,代码如下。

if(ha.dns_wc_tail.nelts){

hash.hash=NULL;

//注意,ngx_hash_wildcard_init方法需要使用临时内存池

hash.temp_pool=ha.temp_pool;

if(ngx_hash_wildcard_init(&hash,ha.dns_wc_tail.elts,

ha.dns_wc_tail.nelts)!=NGX_OK)

{

return NGX_ERROR;

}

/ngx_hash_init_t中的hash指针是ngx_hash_wildcard_init初始化成功的散列表,需要将它赋到combinedHash.wc_tail后置通配符散列表指针中/

combinedHash.wc_tail=(ngx_hash_wildcard_t*)hash.hash;

}


到此,临时内存池已经没有存在的意义了,也就是说,ngx_hash_keys_arrays_t中的这些数组、简易散列表都可以销毁了。这时,只需要简单地把temp_pool内存池销毁就可以了,代码如下。


ngx_destroy_pool(ha.temp_pool);


下面检查一下散列表是否工作正常。首先,查询关键字www.test.org,实际上,它应该匹配后置通配符散列表中的元素www.test.*,代码如下。


//首先定义待查询的关键字字符串findServer

ngx_str_t findServer;

findServer.len=ngx_strlen("www.test.org");

/为什么必须要在内存池中分配空间以保存关键字呢?因为我们使用的散列方法是ngx_hash_key_lc,它会试着把关键字全小写/

findServer.data=ngx_pcalloc(cf->pool,ngx_strlen("www.test.org"));

ngx_memcpy(findServer.data,"www.test.org",ngx_strlen("www.test.org"));

/ngx_hash_find_combined方法会查找出www.test.对应的散列表元素,返回其指向的用户数据ngx_hash_find_combined,也就是testHashNode[1]*/

TestWildcardHashNode*findHashNode=

ngx_hash_find_combined(&combinedHash,

ngx_hash_key_lc(findServer.data,findServer.len),

findServer.data,findServer.len);


如果没有查询到的话,那么findHashNode值为NULL空指针。

下面试着查询www.test.com,实际上,testHashNode[0]、testHashNode[1]、testHashNode[2]这3个节点都是匹配的,因为.test.com、www.test.、www.test.com明显都是匹配的。但按照完全匹配最优先的规则,ngx_hash_find_combined方法会返回testHashNode[2]的地址,也就是www.test.com对应的元素。


findServer.len=ngx_strlen("www.test.com");

findServer.data=ngx_pcalloc(cf->pool,ngx_strlen("www.test.com"));

ngx_memcpy(findServer.data,"www.test.com",ngx_strlen("www.test.com"));

findHashNode=ngx_hash_find_combined(&combinedHash,

ngx_hash_key_lc(findServer.data,findServer.len),

findServer.data,findServer.len);


下面测试一下后置通配符散列表。如果查询的关键字是"smtp.test.com",那么查询到的应该是关键字为*.test.com的元素testHashNode[0]。


findServer.len=ngx_strlen("smtp.test.com");

findServer.data=ngx_pcalloc(cf->pool,ngx_strlen("smtp.test.com"));

ngx_memcpy(findServer.data,"smtp.test.com",ngx_strlen("smtp.test.com"));

findHashNode=ngx_hash_find_combined(&combinedHash,

ngx_hash_key_lc(findServer.data,findServer.len),

findServer.data,findServer.len);