在一个老牌通信公司工作几年,做的核心网项目开发,整天关注的是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);</span> 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; i<ht->numBuckets; 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表的操作中套用双向链表的操作方法。