在一个老牌通信公司工作几年,做的核心网项目开发,整天关注的是call flow,所参与的项目基本和MM和Call Control相关,
大多重点在项目开发逻辑,以及信令的解析和分析上。
公司内部代码有很多优秀的设计细节,和架构方式,只不过平时在此宏观的架构的基础上添加新代码,使用优秀简洁的接口,很少
仔细分析这些代码的优势所在。其中包括网络通信,进程间通信,以及多线程,共享内存,进程管理,状态机,以及定时器,
各种log机制。
最近有了一些闲暇时间,准备细细阅读基础代码,将一些优秀的设计理念和方法,加以整理总结。
首先看一下常用的hash表与双向链表结组合使用,这样的组合一般使用在网络通讯中,接收端收到大量的新数据消息,
将消息先存储到hash中,查询快捷,方便。
哈希表是由一个表头加上若干个节点组成,每个节点都是一个双向链表,数据对象实际是存储在哈希表节点的双向链表节点中。
由于每个节点都是一个双向链表,链表的每个节点中存储数据。
这样的好处是,如果随机产生的hash值如果相同,也可以使用,也不用重新产生命中的相同的hash值,可以直接存入对应的双向链表中。
这需要一个很好的散列化的hash函数,将数据对象均匀分布到节点的链表中。
哈希表的表头中定义了:
1. 哈希表的节点的最大个数变量
2. 产生哈希值的哈希函数指针变量的定义
3. 用于查找的对比函数指针变量的定义
4. 定义存储节点的数组
5. 存储数据对象的个数
双向链表定义:
1. 带有双向指针的表头
2. 带有双向指针的表尾
3. 带有双向链表中存储数据的个数
哈希表的表头定义如下:
/**
* data structure of hash table
*/
typedef struct _HASHTABLE
{
/// static configurable data of hash table
int numBuckets; /// number of bucket in this hash table
HASHFUNCPTR hashFunc; /// hash function from key to bucket
COMPFUNCPTR cmpFunc; /// comparison function to the key of the object
DLIST_P bucketArray; /// array of buckets in this hash table
/// dynamic data of hash table
int numObjects; /// number of objects in this hash table
} HASHTABLE_T, *HASHTABLE_P;
双向链表的定义如下:
/**
*
* The double linked list is used to manage the list of objects for specific purpose.
* It allows the user to insert the object into or remove it from the list dynamically.
*
* To link them together, the object MUST contain GEN_NODE_T as the first element in its
* structure, so that we can cast the pointer to the object to the GEN_NODE_P.
*
* The structure of double linked list looks like the following.
*
* +----------+ +----------+ +----------+
* NULL <---+-- prev |<--------+-- prev |<-------- <--------+-- prev |
* +----------+ +----------+ ... +----------+
* | next --+-------->| next --+--------> -------->| next --+--->NULL
* +----------+ +----------+ +----------+
* | data | | data | | data |
* +----------+ +----------+ +----------+
* ^ ^
* | |
* | |
* head(list) tail(list)
*
*/
/**
* data structure of double linked list
*/
typedef struct _DLIST
{
/// dynamic data of this list
GEN_NODE_P head; /// pointer to the head node of this list
GEN_NODE_P tail; /// pointer to the tail node of this list
int numObjects; /// current number of nodes in this list
} DLIST_T, *DLIST_P;
/**
* data structure of general purpose node used in double linked list
*
*/
typedef struct _GEN_NODE {
struct _GEN_NODE *prev; /// pointer to the previous node in the list
struct _GEN_NODE *next; /// pointer to the next node in the list
} GEN_NODE_T, *GEN_NODE_P;
哈希表的创建:创建函数中有带入两个函数指针类型的函数,一个是hash函数,一个是对比函数,在头文件中事先定义好对应的函数指针。
哈希函数指针的定义
typedef unsigned int (* HASHFUNCPTR)(const void *key);
对比函数指针的定义
typedef int (* COMPFUNCPTR)(const void * obj, const void *key);
HASHTABLE_P HashTable_create(int numBuckets, HASHFUNCPTR hashFunc, COMPFUNCPTR cmpFunc)
{
HASHTABLE_P ht = NULL;
assert((hashFunc != NULL) && (cmpFunc != NULL) && (numBuckets > 0));
ht = (HASHTABLE_P)calloc(1, sizeof(HASHTABLE_T) + sizeof(DLIST_T) * numBuckets);
if (ht != NULL) {
ht->numBuckets = numBuckets;
ht->hashFunc = hashFunc;
ht->cmpFunc = cmpFunc;
ht->bucketArray = (DLIST_P)(ht + 1);
}
return ht;
}
这个函数是创建整个哈希表,为哈希表申请内存空间,包括哈希表的表头,还同时创建了numBuckets个双向链表。
在此用的申请空间的函数是calloc,在申请完空间后,会自动将空间清空。这是一个好的使用方法。
将哈希函数和对比函数的函数指针,赋入函数。
具体使用方法如下:
/* Max Number of Active CSFB CALL in Queue */
#define MAX_CSFB_CALL 4096
g_pPgrHash = HashTable_create(MAX_CSFB_CALL, s102PgrHash, s102PgrCmp);
if (g_pPgrHash == NULL)
{
UX_ID_ELOG(ES1020623, "Unable to HashTable_create g_pPgrHash \n");
UXTPCU(A21_KILL_SIG);
}
销毁整个哈希表:销毁整个hash表,可以先调用清空此哈希表中所有数据的值,然后将申请的hash表的内存空间free掉。
void HashTable_destroy(HASHTABLE_P ht, FREEFUNCPTR freeFunc)
{
assert(ht != NULL);
// delete all the object from each bucket
HashTable_delAllObjs(ht, freeFunc);
// release the memory for hashtable
free(ht);
return;
}
可以根据查找函数HashTable_getBucket(),此函数的功能是根据key值,调用hash函数,产生hash值,返回对应数组中双向链表的地址。
然后调用双向链表的插入函数,将数据加入到双向链表的表尾处。
int HashTable_addObj(HASHTABLE_P ht, void *key, void *obj)
{
DLIST_P bucket = NULL;
assert((ht != NULL) && (key != NULL) && (obj != NULL));
bucket = HashTable_getBucket(ht, key);
if (bucket == NULL) return HASHTABLE_ERROR_HASHFAIL;
if (DList_addObjTail(bucket, obj) != DLIST_NO_ERROR) {
return HASHTABLE_ERROR_INSERTFAIL;
}
ht->numObjects++;
return HASHTABLE_NO_ERROR;
}
双向链表添加数据的函数如下:
int DList_addObjTail(DLIST_P list, void *obj)
{
assert((list != NULL) && (obj != NULL));
GEN_NODE_P node = (GEN_NODE_P)obj;
node->prev = list->tail;
node->next = NULL;
if (list->tail == NULL) {
if ((list->head != NULL) || (list->numObjects != 0)) {
return DLIST_ERROR_INCONSISTENT;
}
list->head = node;
} else {
node->prev->next = node;
}
list->tail = node;
list->numObjects++;
return DLIST_NO_ERROR;
}
OBJ即为插入的数据对象,此数据对象有个要求,此数据结构的第一个变量必须是GEN_NODE_T link;
双向链表可以从head往里插入数据,也可从tail处插入数据。
此次是从tail处插入数据。
1. 首先将数据对象的首地址强制转换成节点类型,这就是为什么数据对象的结构体的第一个变量为什么必须是GEN_NODE_T 类型的缘故,将数据前后链接。
2. 从结尾处插入数据,将此节点的前驱指向双向链表的tail,将此节点的后继设为空。
3. 由于是双向链表,前一个数据,需要将后继改为新加入的节点,如果此链表为空,可直接将链表的头指向此数据对象节点。
4.从结尾出插入数据,新加入的数据即为tail,将链表的tail指向新加入的数据节点
5. 将链表中数据对象的个数加1
使用方法如下:
// add this csr into hash table with given key
retVal = HashTable_addObj(g_pCsrHash, (void *)mnidPtr, (void *)&g_s102csr[objId]);
if (retVal != HASHTABLE_NO_ERROR) {
UX_ID_ELOG( ES1020576, "Failed to call HashTable_addObj to add the csr index %d : retval: %d", objI\
d, retVal);
/// free the Correlation Id for other request message
Allocator_freeObject(g_pCsrIdleList, objId);
index = -1;
}
哈希表查找数据:
void *HashTable_findObj(HASHTABLE_P ht, void *key, DLIST_P *bucket)
{
DLIST_P bkt = NULL;
void *obj = NULL;
assert((ht != NULL) && (key != NULL));
bkt = HashTable_getBucket(ht, key);
if (bkt != NULL) {
obj = DList_findObj(bkt, ht->cmpFunc, key);
}
if (bucket != NULL) *bucket = bkt;
return obj;
}
根据查找函数HashTable_getBucket 获取key值应该放得节点位置
根据key值,调用hash函数,产生hash值,返回对应数组中双向链表的地址。
根据对比key,在此双向链表中查询此对象key值,如果找到,返回对应节点的地址。根据节点地址,可获取此节点中对象的数据。
从双向链表中查找数据的函数如下:
void* DList_findObj(DLIST_P list, COMPFUNCPTR cmpFunc, void *key)
{
assert((list != NULL) && (cmpFunc != NULL) && (key != NULL));
GEN_NODE_P node = list->head;
int count = 0;
while ((node != NULL) && (count < list->numObjects)) {
if ((* cmpFunc)((void *)node, key) == 0) return (void *)node;
count++;
node = node->next;
}
return NULL;
}
通过对比函数将key值和链表中的数据key值进行对比,返回节点地址。
void *HashTable_removeObj(HASHTABLE_P ht, void *key)
{
DLIST_P bucket = NULL;
void *obj = NULL;
assert((ht != NULL) && (key != NULL));
obj = HashTable_findObj(ht, key, &bucket);
if (obj != NULL) {
if (bucket != NULL) {
DList_removeObj(bucket, obj);
ht->numObjects--;
} else {
return NULL;
}
}
return obj;
}
在此哈希表中删除一条数据包含了哈希表查找数据,然后再双向链表中将此数据删除,hash表中数据总个数减1。
双向链表删除一个对象的函数如下:
void DList_removeObj(DLIST_P list, void *obj)
{
assert(list != NULL);
GEN_NODE_P node = (GEN_NODE_P)obj;
if (node == NULL) return;
if (node->prev == NULL) {
// if the object is the head of list, set the head to the next object
list->head = node->next;
} else {
// otherwise, let the previous one links to the next object
node->prev->next = node->next;
}
if (node->next == NULL) {
// if the object is the tail of list, set the tail to the previous object
list->tail = node->prev;
} else {
// otherwise, let the next one links to the previous object
node->next->prev = node->prev;
}
// clear the linkage of this object
node->prev = NULL;
node->next = NULL;
list->numObjects--;
return;
}
1. 首先将数据对象的首地址强制转换成节点类型,这就是为什么数据对象的结构体的第一个变量为什么必须是GEN_NODE_T 类型的缘故,将数据前后链接,便于操作。
2. 如果要删除的节点的前驱是空,表明此节点为head,将此节点的后继设为head即可
3. 如果要删除的节点的前驱不是空,将此节点前驱的后继指向此节点的后继。
4. 如果要删除的节点的后继是空,表明此节点为tail,将此节点的前驱设为tail即可
5. 如果要删除的节点的后继不是空,将此节点后继的前驱设为此节点的前驱
6. 将此节点前驱后继,设为null
7.链表中的数据总个数减1
void HashTable_delAllObjs(HASHTABLE_P ht, FREEFUNCPTR freeFunc)
{
int i;
assert(ht != NULL);
for (i=0; inumBuckets; i++) {
DList_delAllObjs(&ht->bucketArray[i], freeFunc);
}
ht->numObjects = 0;
return;
}
int HashTable_getSize(HASHTABLE_P ht)
{
assert(ht);
if (ht->numObjects < 0) return HASHTABLE_ERROR_INCONSISTENT;
return ht->numObjects;
}
DLIST_P HashTable_getBucket(HASHTABLE_P ht, void *key)
{
int slot = 0;
assert((ht != NULL) && (key != NULL));
slot = (* ht->hashFunc)(key);
if ((slot < 0) || (slot >= ht->numBuckets)) return NULL;
return &ht->bucketArray[slot];
}
根据key值,调用hash函数,产生hash值,返回对应数组中双向链表的地址。
以上为hash表的操作中套用双向链表的操作方法。