赞
踩
最近在做FTP服务器项目的时候,需要统计服务器的最大连接数和每IP连接数。
每IP连接数:指的是服务器连接同一个IP地址的次数
最大连接数:服务器最多可以连接客户端的个数
其中前者需要用到哈希表。
c++中,我们可以直接使用map或者unordered_map来产生pair键值对,c语言中没有对应的库,所以需要自己实现一下hash表的结构。
如下图所示:
一般哈希函数选择的是除留余数法。
hash(key)=key%p,其中p≤buckets,取最接近于buckets的质数即可。
需要注意的是,由于经过哈希函数映射之后,关键码对应的位置对应滴位置可能是相同的,因此采用开散列的方法,使用链表的结构将所有相同位置的关键码串联起来。
如下图所示:为了方便起见,11,22,33处对应的是哈希结点的地址而不是key值。链表的插入采用的是头插,提升效率。
例如key=0,11,22,33对应的地址都是相同的,可以采用链表的方式将其连接。
1.hash_node的结构:
typedef struct hash_node
{
void *key;
void *node;
struct hash_node* prev;
struct hash_node* next;
}hash_node_t;
2.hash表的结构
//函数指针 对应的函数名是hashfunc_t:参数一个为int,一个为void。经过typedef之后,可以通过hashfunc_t来代表函数指针。
typedef unsigned int (*hashfunc_t)(unsigned int, void*);
struct hash
{
unsigned int buckets; //对应的桶的大小
hashfunc_t hash_func; //保存的hash函数的地址
hash_node_t **nodes; //二级指针,存放桶结点的首地址
};
3.hash表的生成
类型的重定义
typedef struct hash hash_t; 将hash表重定义为hash_t
hash_func是自己定义的,这里存放的是函数地址。定义的hash_func如下所示:
unsigned int hash_func(unsigned int bucket_size, void* key)
{
return (*(unsigned int*)key) % bucket_size;
}
注意,这里结点的大小是桶结点的个数乘以每一个指针的内存大小,对hash_nodes所指的内存空间进行内存的初始化,指针置为NULL。
hash_t *hash_alloc(unsigned int buckets, hashfunc_t hash_func)
{
hash_t *hash = (hash_t *)malloc(sizeof(hash_t));
assert(hash != NULL);
hash->buckets = buckets;
hash->hash_func = hash_func;
int size = buckets * sizeof(hash_node_t *);
hash->nodes = (hash_node_t **)malloc(size);
memset(hash->nodes, 0, size);
return hash;
}
下面部分是获取关键码对应的桶结点的位置,通过hash函数映射之后计算出对应的bucket,将对应结点的地址返回。
hash_node_t** hash_get_bucket(hash_t* hash, void* key)
{
unsigned int bucket = hash->hash_func(hash->buckets, key);
if (bucket >= hash->buckets)
{
fprintf(stderr, "bad bucket lookup\n");
exit(1);
}
return &(hash->nodes[bucket]);
}
下面部分是通过关键值key来查询对应的结点,例如我们需要查找key=22的结点,找到buckets的位置之后,链表向后遍历即可。
之前自己理解的时侯,总觉得桶结点已经找到了,为什么还需要向后遍历呢,其实是需要的,因为当前桶的头结点存储的key值可能不是我们需要找的,比如上面的例子中,buckets[0]存储的是key=0的元素的地址。
hash_node_t* hash_get_node_by_key(hash_t* hash, void* key, unsigned int key_size)
{
hash_node_t** bucket = hash_get_bucket(hash, key);
hash_node_t* node = *bucket;
if (node == NULL)
{
return NULL;
}
while (node != NULL && memcmp(node->key, key, key_size) != 0)
{
node = node->next;
}
return node;
}
代码如下所示:如果找到key值,就返回当前key值对应的value值。否则返回NULL
void* hash_lookup_entry(hash_t* hash, void* key, unsigned int key_size)
{
hash_node_t* node = hash_get_node_by_key(hash, key, key_size);
if (node == NULL)
{
return NULL;
}
return node->value;
}
需要给定key值的地址,对应的大小,value值的地址,对应的大小。
void hash_add_entry(hash_t* hash, void* key, unsigned int key_size, void* value, unsigned int value_size) { if (hash_lookup_entry(hash, key, key_size)) { fprintf(stderr, "duplicate hash key\n"); return; } hash_node_t* node = malloc(sizeof(hash_node_t)); node->prev = NULL; node->next = NULL; node->key = malloc(key_size); memcpy(node->key, key, key_size); node->value = malloc(value_size); memcpy(node->value, value, value_size); hash_node_t** bucket = hash_get_bucket(hash, key); if (*bucket == NULL) { *bucket = node; } else { // 将新结点插入到链表头部 // 当前结点的后继指向下一个结点。 node->next = *bucket; // (*bucket)->prev = node; *bucket = node; } }
void hash_free_entry(hash_t* hash, void* key, unsigned int key_size) { hash_node_t* node = hash_get_node_by_key(hash, key, key_size); if (node == NULL) { return; } free(node->key); free(node->value); if (node->prev) { // 第一步 node->prev->next = node->next; } else { hash_node_t** bucket = hash_get_bucket(hash, key); *bucket = node->next; } // 后继结点的前驱连接被删除结点的前驱(第二步) if (node->next) node->next->prev = node->prev; // 释放结点 free(node); }
附录:假设给定的key值不是int值,要怎么处理呢?参考了一下别人的代码:将对应的字符串转为相对应的ASII值,再进行计算即可。
unsigned int hash_func_str(unsigned int bucket_size, char* key)
{
int sum = 0;
char* tmp = key;
while (*tmp != '\0')
{
sum += *tmp;
tmp++;
}
return sum % bucket_size;
}
1.key值为数字的时候
void printhash(hash_t* hash1)
{
hash_node_t** z = hash1->nodes;
int i = 0;
for (i = 0; i < hash1->buckets; ++i)
{
if (z[i] == NULL)
continue;
while (z[i]!= NULL)
{
printf("key:%d,value:%d\n", *(int*)(z[i]->key), *(int*)(z[i]->value));
z[i] = z[i]->next;
}
}
}
2.key值为字符串的时候
void printhash(hash_t* hash1)
{
hash_node_t** z = hash1->nodes;
int i = 0;
for (i = 0; i < hash1->buckets; ++i)
{
if (z[i] == NULL)
continue;
while (z[i]!= NULL)
{
printf("key:%s,value:%d\n", (char*)z[i]->key, *(int*)(z[i]->value));
z[i] = z[i]->next;
}
}
}
测试函数如下所示:
static struct hash* ip_count_hash; int main() { ip_count_hash = hash_alloc(17, hash_func_str); char key1[] = "ab"; //printf("%d\n", sizeof(key1)); int value1 = 100; char key2[] = "bc"; int value2 = 20; char key3[] = "cde"; int value3 = 30; hash_add_entry(ip_count_hash, &key1, sizeof(key1), &value1,sizeof(int)); hash_add_entry(ip_count_hash, &key2, sizeof(key2), &value2, sizeof(int)); hash_add_entry(ip_count_hash, &key3, sizeof(key3), &value3, sizeof(int)); printhash(ip_count_hash); return 0; }
后续再研究闭散列吧,c语言和c++的实现差距还是挺大的,本篇主要是为了FTP服务器的IP数目限制所写的,比较简单一点,有兴趣的同学可以参考一下这篇博客哦
FTP项目第三部分
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。