出处: http://blog.csdn.net/luotuo44/article/details/38403241
上一篇博客说到了TAILQ_QUEUE队列,它可以把多个event结构体连在一起。是一种归类方式。本文也将讲解一种将event归类、连在一起的结构:哈希结构。
哈希结构体:
哈希结构由下面几个结构体一起配合工作:
- struct event_list
- {
- struct event *tqh_first;
- struct event **tqh_last;
- };
- struct evmap_io {
- //TAILQ_HEAD (event_list, event);
- struct event_list events;
- ev_uint16_t nread;
- ev_uint16_t nwrite;
- };
- struct event_map_entry {
- HT_ENTRY(event_map_entry) map_node; //next指针
- evutil_socket_t fd;
- union { /* This is a union in case we need to make more things that can
- be in the hashtable. */
- struct evmap_io evmap_io;
- } ent;
- };
- struct event_io_map
- {
- //哈希表
- struct event_map_entry **hth_table;
- //哈希表的长度
- unsigned hth_table_length;
- //哈希的元素个数
- unsigned hth_n_entries;
- //resize 之前可以存多少个元素
- //在event_io_map_HT_GROW函数中可以看到其值为hth_table_length的
- //一半。但hth_n_entries>=hth_load_limit时,就会发生增长哈希表的长度
- unsigned hth_load_limit;
- //后面素数表中的下标值。主要是指明用到了哪个素数
- int hth_prime_idx;
- };
结构体event_io_map指明了哈希表的存储位置、哈希表的长度、元素个数等数据。该哈希表是使用链地址法解决冲突问题的,这一点可以从hth_talbe成员变量看到。它是一个二级指针,因为哈希表的元素是event_map_entry指针。
除了怎么解决哈希冲突外,哈希表还有一个问题要解决,那就是哈希函数。这里的哈希函数就是模(%)。用event_map_entry结构体中的fd成员值模 event_io_map结构体中的hth_table_length。
由上面那些结构体配合得到的哈希表结构如下图所示:
从上图可以看到,两个发生了冲突的哈希表元素event_map_entry用一个next指向连在一起了(链地址解决冲突)。
另外,从图或者从前面关于event_map_entry结构体的定义可以看到,它有一个evmap_io结构体。而这个evmap_io结构体又有一个struct event_list 类型的成员,而struct event_list类型就是一个TAILQ_HEAD。这正是前一篇博客说到的TAILQ_QUEUE队列的队列头。从这一点可以看到,这个哈希结构还是比较复杂的。
为什么在哈希表的元素里面,还会有一个TAILQ_QUEUE队列呢?这得由Libevent的一个特征说起。Libevent允许用同一个文件描述符fd或者信号值,调用event_new、event_add多次。所以,同一个fd或者信号值就可以对应多个event结构体了。所以这个TAILQ_QUEUE队列就是将这些具有相同fd或者信号值的多个event结构体连一起。
什么情况会使用哈希表:
有一点需要说明,那就是Libevent中的哈希表只会用于Windows系统,像遵循POSIX标准的OS是不会用到哈希表的。从下面的定义可以看到这一点。
- //event-internal.h文件
- #ifdef WIN32
- /* If we're on win32, then file descriptors are not nice low densely packed
- integers. Instead, they are pointer-like windows handles, and we want to
- use a hashtable instead of an array to map fds to events.
- */
- #define EVMAP_USE_HT
- #endif
- #ifdef EVMAP_USE_HT
- #include "ht-internal.h"
- struct event_map_entry;
- HT_HEAD(event_io_map, event_map_entry);
- #else
- #define event_io_map event_signal_map
- #endif
可以看到,如果是非Windows系统,那么event_io_map就会被定义成event_signal_map(这是一个很简单的结构)。而在Windows系统,那么就由HT_HEAD这个宏定义event_io_map。最后得到的event_io_map就是本文最前面所示的那样。
为什么只有在Windows系统才会使用这个哈希表呢?代码里面的注释给出了一些解释。因为在Windows系统里面,文件描述符是一个比较大的值,不适合放到event_signal_map结构中。而通过哈希(模上一个小的值),就可以变得比较小,这样就可以放到哈希表的数组中了。而遵循POSIX标准的文件描述符是从0开始递增的,一般都不会太大,适用于event_signal_map。
哈希函数:
前面说到哈希函数是 用文件描述符fd 模 哈希表的长度。实际上,并不是直接用fd,而是用一个叫hashsocket的函数将这个fd进行一些处理后,才去模 哈希表的长度。下面是hashsocket函数的实现:
- //evmap.c文件
- /* Helper used by the event_io_map hashtable code; tries to return a good hash
- * of the fd in e->fd. */
- static inline unsigned
- hashsocket(struct event_map_entry *e)
- {
- /* On win32, in practice, the low 2-3 bits of a SOCKET seem not to
- * matter. Our hashtable implementation really likes low-order bits,
- * though, so let's do the rotate-and-add trick. */
- unsigned h = (unsigned) e->fd;
- h += (h >> 2) | (h << 30);
- return h;
- }
前面的event_map_entry结构体中,还有一个HT_ENTRY的宏。从名字来看,它是一个哈希表的表项。它是一个条件宏,定义如下:
- //ht-internal.h文件
- #ifdef HT_CACHE_HASH_VALUES
- #define HT_ENTRY(type) \
- struct { \
- struct type *hte_next; \
- unsigned hte_hash; \
- }
- #else
- #define HT_ENTRY(type) \
- struct { \
- struct type *hte_next; \
- }
- #endif
哈希表操作函数:
ht-internal.h文件里面定义了一系列的哈希函数的操作函数。下来就列出这些函数。如果打开ht-internal.h文件,就发现,它是宏的天下。该文件的函数都是由宏定义生成的。下面就贴出宏定义展开后的函数。同前一篇博文那样,是用gcc的-E选项展开的。下面的代码比较长,要有心理准备。
- struct event_list
- {
- struct event *tqh_first;
- struct event **tqh_last;
- };
- struct evmap_io
- {
- struct event_list events;
- ev_uint16_t nread;
- ev_uint16_t nwrite;
- };
- struct event_map_entry
- {
- struct
- {
- struct event_map_entry *hte_next;
- #ifdef HT_CACHE_HASH_VALUES
- unsigned hte_hash;
- #endif
- }map_node;
- evutil_socket_t fd;
- union
- {
- struct evmap_io evmap_io;
- }ent;
- };
- struct event_io_map
- {
- //哈希表
- struct event_map_entry **hth_table;
- //哈希表的长度
- unsigned hth_table_length;
- //哈希的元素个数
- unsigned hth_n_entries;
- //resize 之前可以存多少个元素
- //在event_io_map_HT_GROW函数中可以看到其值为hth_table_length的
- //一半。但hth_n_entries>=hth_load_limit时,就会发生增长哈希表的长度
- unsigned hth_load_limit;
- //后面素数表中的下标值。主要是指明用到了哪个素数
- int hth_prime_idx;
- };
- int event_io_map_HT_GROW(struct event_io_map *ht, unsigned min_capacity);
- void event_io_map_HT_CLEAR(struct event_io_map *ht);
- int _event_io_map_HT_REP_IS_BAD(const struct event_io_map *ht);
- //初始化event_io_map
- static inline void event_io_map_HT_INIT(struct event_io_map *head)
- {
- head->hth_table_length = 0;
- head->hth_table = NULL;
- head->hth_n_entries = 0;
- head->hth_load_limit = 0;
- head->hth_prime_idx = -1;
- }
- //在event_io_map这个哈希表中,找个表项elm
- //在下面还有一个相似的函数,函数名少了最后的_P。那个函数的返回值
- //是event_map_entry *。从查找来说,后面那个函数更适合。之所以
- //有这个函数,是因为哈希表还有replace、remove这些操作。对于
- //A->B->C这样的链表。此时,要replace或者remove节点B。
- //如果只有后面那个查找函数,那么只能查找并返回一个指向B的指针。
- //此时将无法修改A的next指针了。所以本函数有存在的必要。
- //在本文件中,很多函数都使用了event_map_entry **。
- //因为event_map_entry **类型变量,既可以修改本元素的hte_next变量
- //也能指向下一个元素。
- //该函数返回的是查找节点的前驱节点的hte_next成员变量的地址。
- //所以返回值肯定不会为NULL,但是对返回值取*就可能为NULL
- static inline struct event_map_entry **
- _event_io_map_HT_FIND_P(struct event_io_map *head,
- struct event_map_entry *elm)
- {
- struct event_map_entry **p;
- if (!head->hth_table)
- return NULL;
- #ifdef HT_CACHE_HASH_VALUES
- p = &((head)->hth_table[((elm)->map_node.hte_hash)
- % head->hth_table_length]);
- #else
- p = &((head)->hth_table[(hashsocket(*elm))%head->hth_table_length]);
- #endif
- //这里的哈希表是用链地址法解决哈希冲突的。
- //上面的 % 只是找到了冲突链的头。现在是在冲突链中查找。
- while (*p)
- {
- //判断是否相等。在实现上,只是简单地根据fd来判断是否相等
- if (eqsocket(*p, elm))
- return p;
- //p存放的是hte_next成员变量的地址
- p = &(*p)->map_node.hte_next;
- }
- return p;
- }
- /* Return a pointer to the element in the table 'head' matching 'elm',
- * or NULL if no such element exists */
- //在event_io_map这个哈希表中,找个表项elm
- static inline struct event_map_entry *
- event_io_map_HT_FIND(const struct event_io_map *head,
- struct event_map_entry *elm)
- {
- struct event_map_entry **p;
- struct event_io_map *h = (struct event_io_map *) head;
- #ifdef HT_CACHE_HASH_VALUES
- do
- { //计算哈希值
- (elm)->map_node.hte_hash = hashsocket(elm);
- } while(0);
- #endif
- p = _event_io_map_HT_FIND_P(h, elm);
- return p ? *p : NULL;
- }
- /* Insert the element 'elm' into the table 'head'. Do not call this
- * function if the table might already contain a matching element. */
- static inline void
- event_io_map_HT_INSERT(struct event_io_map *head,
- struct event_map_entry *elm)
- {
- struct event_map_entry **p;
- if (!head->hth_table || head->hth_n_entries >= head->hth_load_limit)
- event_io_map_HT_GROW(head, head->hth_n_entries+1);
- ++head->hth_n_entries;
- #ifdef HT_CACHE_HASH_VALUES
- do
- { //计算哈希值.此哈希不同于用%计算的简单哈希。
- //存放到hte_hash变量中,作为cache
- (elm)->map_node.hte_hash = hashsocket(elm);
- } while (0);
- p = &((head)->hth_table[((elm)->map_node.hte_hash)
- % head->hth_table_length]);
- #else
- p = &((head)->hth_table[(hashsocket(*elm))%head->hth_table_length]);
- #endif
- //使用头插法,即后面才插入的链表,反而会在链表头。
- elm->map_node.hte_next = *p;
- *p = elm;
- }
- /* Insert the element 'elm' into the table 'head'. If there already
- * a matching element in the table, replace that element and return
- * it. */
- static inline struct event_map_entry *
- event_io_map_HT_REPLACE(struct event_io_map *head,
- struct event_map_entry *elm)
- {
- struct event_map_entry **p, *r;
- if (!head->hth_table || head->hth_n_entries >= head->hth_load_limit)
- event_io_map_HT_GROW(head, head->hth_n_entries+1);
- #ifdef HT_CACHE_HASH_VALUES
- do
- {
- (elm)->map_node.hte_hash = hashsocket(elm);
- } while(0);
- #endif
- p = _event_io_map_HT_FIND_P(head, elm);
- //由前面的英文注释可知,这个函数是替换插入一起进行的。如果哈希表
- //中有和elm相同的元素(指的是event_map_entry的fd成员变量值相等)
- //那么就发生替代(其他成员变量值不同,所以不是完全相同,有替换意义)
- //如果哈希表中没有和elm相同的元素,那么就进行插入操作
- //指针p存放的是hte_next成员变量的地址
- //这里的p存放的是被替换元素的前驱元素的hte_next变量地址
- r = *p; //r指向了要替换的元素。有可能为NULL
- *p = elm; //hte_next变量被赋予新值elm
- //找到了要被替换的元素r(不为NULL)
- //而且要插入的元素地址不等于要被替换的元素地址
- if (r && (r!=elm))
- {
- elm->map_node.hte_next = r->map_node.hte_next;
- r->map_node.hte_next = NULL;
- return r; //返回被替换掉的元素
- }
- else //进行插入操作
- {
- //这里貌似有一个bug。如果前一个判断中,r 不为 NULL,而且r == elm
- //对于同一个元素,多次调用本函数。就会出现这样情况。
- //此时,将会来到这个else里面
- //实际上没有增加元素,但元素的个数却被++了。因为r 的地址等于 elm
- //所以 r = *p; *p = elm; 等于什么也没有做。(r == elm)
- //当然,因为这些函数都是Libevent内部使用的。如果它保证不会同于同
- //一个元素调用本函数,那么就不会出现bug
- ++head->hth_n_entries;
- return NULL; //插入操作返回NULL,表示没有替换到元素
- }
- }
- /* Remove any element matching 'elm' from the table 'head'. If such
- * an element is found, return it; otherwise return NULL. */
- static inline struct event_map_entry *
- event_io_map_HT_REMOVE(struct event_io_map *head,
- struct event_map_entry *elm)
- {
- struct event_map_entry **p, *r;
- #ifdef HT_CACHE_HASH_VALUES
- do
- {
- (elm)->map_node.hte_hash = hashsocket(elm);
- } while (0);
- #endif
- p = _event_io_map_HT_FIND_P(head,elm);
- if (!p || !*p)//没有找到
- return NULL;
- //指针p存放的是hte_next成员变量的地址
- //这里的p存放的是被替换元素的前驱元素的hte_next变量地址
- r = *p; //r现在指向要被删除的元素
- *p = r->map_node.hte_next;
- r->map_node.hte_next = NULL;
- --head->hth_n_entries;
- return r;
- }
- /* Invoke the function 'fn' on every element of the table 'head',
- using 'data' as its second argument. If the function returns
- nonzero, remove the most recently examined element before invoking
- the function again. */
- static inline void
- event_io_map_HT_FOREACH_FN(struct event_io_map *head,
- int (*fn)(struct event_map_entry *, void *),
- void *data)
- {
- unsigned idx;
- struct event_map_entry **p, **nextp, *next;
- if (!head->hth_table)
- return;
- for (idx=0; idx < head->hth_table_length; ++idx)
- {
- p = &head->hth_table[idx];
- while (*p)
- {
- //像A->B->C链表。p存放了A元素中hte_next变量的地址
- //*p则指向B元素。nextp存放B的hte_next变量的地址
- //next指向C元素。
- nextp = &(*p)->map_node.hte_next;
- next = *nextp;
- //对B元素进行检查
- if (fn(*p, data))
- {
- --head->hth_n_entries;
- //p存放了A元素中hte_next变量的地址
- //所以*p = next使得A元素的hte_next变量值被赋值为
- //next。此时链表变成A->C。即使抛弃了B元素。不知道
- //调用方是否能释放B元素的内存。
- *p = next;
- }
- else
- {
- p = nextp;
- }
- }
- }
- }
- /* Return a pointer to the first element in the table 'head', under
- * an arbitrary order. This order is stable under remove operations,
- * but not under others. If the table is empty, return NULL. */
- //获取第一条冲突链的第一个元素
- static inline struct event_map_entry **
- event_io_map_HT_START(struct event_io_map *head)
- {
- unsigned b = 0;
- while (b < head->hth_table_length)
- {
- //返回哈希表中,第一个不为NULL的节点
- //即有event_map_entry元素的节点。
- //找到链。因为是哈希。所以不一定哈希表中的每一个节点都存有元素
- if (head->hth_table[b])
- return &head->hth_table[b];
- ++b;
- }
- return NULL;
- }
- /* Return the next element in 'head' after 'elm', under the arbitrary
- * order used by HT_START. If there are no more elements, return
- * NULL. If 'elm' is to be removed from the table, you must call
- * this function for the next value before you remove it.
- */
- static inline struct event_map_entry **
- event_io_map_HT_NEXT(struct event_io_map *head,
- struct event_map_entry **elm)
- {
- //本哈希解决冲突的方式是链地址
- //如果参数elm所在的链地址中,elm还有下一个节点,就直接返回下一个节点
- if ((*elm)->map_node.hte_next)
- {
- return &(*elm)->map_node.hte_next;
- }
- else //否则取哈希表中的下一条链中第一个元素
- {
- #ifdef HT_CACHE_HASH_VALUES
- unsigned b = (((*elm)->map_node.hte_hash)
- % head->hth_table_length) + 1;
- #else
- unsigned b = ( (hashsocket(*elm)) % head->hth_table_length) + 1;
- #endif
- while (b < head->hth_table_length)
- {
- //找到链。因为是哈希。所以不一定哈希表中的每一个节点都存有元素
- if (head->hth_table[b])
- return &head->hth_table[b];
- ++b;
- }
- return NULL;
- }
- }
- //功能同上一个函数。只是参数不同,另外本函数还会使得--hth_n_entries
- //该函数主要是返回elm的下一个元素。并且哈希表的总元素个数减一。
- //主调函数会负责释放*elm指向的元素。无需在这里动手
- //在evmap_io_clear函数中,会调用本函数。
- static inline struct event_map_entry **
- event_io_map_HT_NEXT_RMV(struct event_io_map *head,
- struct event_map_entry **elm)
- {
- #ifdef HT_CACHE_HASH_VALUES
- unsigned h = ((*elm)->map_node.hte_hash);
- #else
- unsigned h = (hashsocket(*elm));
- #endif
- //elm变量变成存放下一个元素的hte_next的地址
- *elm = (*elm)->map_node.hte_next;
- --head->hth_n_entries;
- if (*elm)
- {
- return elm;
- }
- else
- {
- unsigned b = (h % head->hth_table_length)+1;
- while (b < head->hth_table_length)
- {
- if (head->hth_table[b])
- return &head->hth_table[b];
- ++b;
- }
- return NULL;
- }
- }
- //素数表。之所以去素数,是因为在取模的时候,素数比合数更有优势。
- //听说是更散,更均匀
- static unsigned event_io_map_PRIMES[] =
- {
- //素数表的元素具有差不多2倍的关系。
- //这使得扩容操作不会经常发生。每次扩容都预留比较大的空间
- 53, 97, 193, 389, 769, 1543, 3079,
- 6151, 12289, 24593, 49157, 98317,
- 196613, 393241, 786433, 1572869, 3145739,
- 6291469, 12582917, 25165843, 50331653, 100663319,
- 201326611, 402653189, 805306457, 1610612741
- };
- //素数表中,元素的个数。
- static unsigned event_io_map_N_PRIMES =
- (unsigned)(sizeof(event_io_map_PRIMES)
- /sizeof(event_io_map_PRIMES[0]));
- /* Expand the internal table of 'head' until it is large enough to
- * hold 'size' elements. Return 0 on success, -1 on allocation
- * failure. */
- int event_io_map_HT_GROW(struct event_io_map *head, unsigned size)
- {
- unsigned new_len, new_load_limit;
- int prime_idx;
- struct event_map_entry **new_table;
- //已经用到了素数表中最后一个素数,不能再扩容了。
- if (head->hth_prime_idx == (int)event_io_map_N_PRIMES - 1)
- return 0;
- //哈希表中还够容量,无需扩容
- if (head->hth_load_limit > size)
- return 0;
- prime_idx = head->hth_prime_idx;
- do {
- new_len = event_io_map_PRIMES[++prime_idx];
- //从素数表中的数值可以看到,后一个差不多是前一个的2倍。
- //从0.5和后的new_load_limit <= size,可以得知此次扩容
- //至少得是所需大小(size)的2倍以上。免得经常要进行扩容
- new_load_limit = (unsigned)(0.5*new_len);
- } while (new_load_limit <= size
- && prime_idx < (int)event_io_map_N_PRIMES);
- if ((new_table = mm_malloc(new_len*sizeof(struct event_map_entry*))))
- {
- unsigned b;
- memset(new_table, 0, new_len*sizeof(struct event_map_entry*));
- for (b = 0; b < head->hth_table_length; ++b)
- {
- struct event_map_entry *elm, *next;
- unsigned b2;
- elm = head->hth_table[b];
- while (elm) //该节点有冲突链。遍历冲突链中所有的元素
- {
- next = elm->map_node.hte_next;
- //冲突链中的元素,相对于前一个素数同余(即模素数后,结果相当)
- //但对于现在的新素数就不一定同余了,即它们不一定还会冲突
- //所以要对冲突链中的所有元素都再次哈希,并放到它们应该在的地方
- //b2存放了再次哈希后,元素应该存放的节点下标。
- #ifdef HT_CACHE_HASH_VALUES
- b2 = (elm)->map_node.hte_hash % new_len;
- #else
- b2 = (hashsocket(*elm)) % new_len;
- #endif
- //用头插法插入数据
- elm->map_node.hte_next = new_table[b2];
- new_table[b2] = elm;
- elm = next;
- }
- }
- if (head->hth_table)
- mm_free(head->hth_table);
- head->hth_table = new_table;
- }
- else
- {
- unsigned b, b2;
- //刚才mm_malloc失败,可能是内存不够。现在用更省内存的
- //mm_realloc方式。当然其代价就是更耗时(下面的代码可以看到)。
- //前面的mm_malloc会同时有hth_table和new_table两个数组。
- //而mm_realloc则只有一个数组,所以省内存,省了一个hth_table数组
- //的内存。此时,new_table数组的前head->hth_table_length个
- //元素存放了原来的冲突链的头部。也正是这个原因导致后面代码更耗时。
- //其实,只有在很特殊的情况下,这个函数才会比mm_malloc省内存.
- //就是堆内存head->hth_table区域的后面刚好有一段可以用的内存。
- //具体的,可以搜一下realloc这个函数。
- new_table = mm_realloc(head->hth_table,
- new_len*sizeof(struct event_map_entry*));
- if (!new_table)
- return -1;
- memset(new_table + head->hth_table_length, 0,
- (new_len - head->hth_table_length)*sizeof(struct event_map_entry*)
- );
- for (b=0; b < head->hth_table_length; ++b)
- {
- struct event_map_entry *e, **pE;
- for (pE = &new_table[b], e = *pE; e != NULL; e = *pE)
- {
- #ifdef HT_CACHE_HASH_VALUES
- b2 = (e)->map_node.hte_hash % new_len;
- #else
- b2 = (hashsocket(*elm)) % new_len;
- #endif
- //对于冲突链A->B->C.
- //pE是二级指针,存放的是A元素的hte_next指针的地址值
- //e指向B元素。
- //对新的素数进行哈希,刚好又在原来的位置
- if (b2 == b)
- {
- //此时,无需修改。接着处理冲突链中的下一个元素即可
- //pE向前移动,存放B元素的hte_next指针的地址值
- pE = &e->map_node.hte_next;
- }
- else//这个元素会去到其他位置上。
- {
- //此时冲突链修改成A->C。
- //所以pE无需修改,还是存放A元素的hte_next指针的地址值
- //但A元素的hte_next指针要指向C元素。用*pE去修改即可
- *pE = e->map_node.hte_next;
- //将这个元素放到正确的位置上。
- e->map_node.hte_next = new_table[b2];
- new_table[b2] = e;
- }
- //这种再次哈希的方式,很有可能会对某些元素操作两次。
- //当某个元素第一次在else中处理,那么它就会被哈希到正确的节点
- //的冲突链上。随着外循环的进行,处理到正确的节点时。在遍历该节点
- //的冲突链时,又会再次处理该元素。此时,就会在if中处理。而不会
- //进入到else中。
- }
- }
- head->hth_table = new_table;
- }
- //一般是当hth_n_entries >= hth_load_limit时,就会调用
- //本函数。hth_n_entries表示的是哈希表中节点的个数。而hth_load_limit
- //是hth_table_length的一半。hth_table_length则是哈希表中
- //哈希函数被模的数字。所以,当哈希表中的节点个数到达哈希表长度的一半时
- //就会发生增长,本函数被调用。这样的结果是:平均来说,哈希表应该比较少发生
- //冲突。即使有,冲突链也不会太长。这样就能有比较快的查找速度。
- head->hth_table_length = new_len;
- head->hth_prime_idx = prime_idx;
- head->hth_load_limit = new_load_limit;
- return 0;
- }
- /* Free all storage held by 'head'. Does not free 'head' itself,
- * or individual elements. 并不需要释放独立的元素*/
- //在evmap_io_clear函数会调用该函数。其是在删除所有哈希表中的元素后
- //才调用该函数的。
- void event_io_map_HT_CLEAR(struct event_io_map *head)
- {
- if (head->hth_table)
- mm_free(head->hth_table);
- head->hth_table_length = 0;
- event_io_map_HT_INIT(head);
- }
- /* Debugging helper: return false iff the representation of 'head' is
- * internally consistent. */
- int _event_io_map_HT_REP_IS_BAD(const struct event_io_map *head)
- {
- unsigned n, i;
- struct event_map_entry *elm;
- if (!head->hth_table_length)
- {
- //刚被初始化,还没申请任何空间
- if (!head->hth_table && !head->hth_n_entries
- && !head->hth_load_limit && head->hth_prime_idx == -1
- )
- return 0;
- else
- return 1;
- }
- if (!head->hth_table || head->hth_prime_idx < 0
- || !head->hth_load_limit
- )
- return 2;
- if (head->hth_n_entries > head->hth_load_limit)
- return 3;
- if (head->hth_table_length != event_io_map_PRIMES[head->hth_prime_idx])
- return 4;
- if (head->hth_load_limit != (unsigned)(0.5*head->hth_table_length))
- return 5;
- for (n = i = 0; i < head->hth_table_length; ++i)
- {
- for (elm = head->hth_table[i]; elm; elm = elm->map_node.hte_next)
- {
- #ifdef HT_CACHE_HASH_VALUES
- if (elm->map_node.hte_hash != hashsocket(elm))
- return 1000 + i;
- if( (elm->map_node.hte_hash % head->hth_table_length) != i)
- return 10000 + i;
- #else
- if ( (hashsocket(*elm)) != hashsocket(elm))
- return 1000 + i;
- if( ( (hashsocket(*elm)) % head->hth_table_length) != i)
- return 10000 + i;
- #endif
- ++n;
- }
- }
- if (n != head->hth_n_entries)
- return 6;
- return 0;
- }
代码中的注释已经对这个哈希表的一些特征进行了描述,这里就不多说了。
哈希表在Libevent的使用:
现在来讲一下event_io_map的应用。
在event_base这个结构体中有一个event_io_map类型的成员变量io。它就是一个哈希表。当一个监听读或者写操作的event,调用event_add函数插入到event_base中时,就会调用evmap_io_add函数。evmap_io_add函数应用到这个event_io_map结构体。该函数的定义如下,其中使用到了一个宏定义,我已经展开了。
- int
- evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)
- {
- const struct eventop *evsel = base->evsel;
- struct event_io_map *io = &base->io;
- struct evmap_io *ctx = NULL;
- int nread, nwrite, retval = 0;
- short res = 0, old = 0;
- struct event *old_ev;
- EVUTIL_ASSERT(fd == ev->ev_fd);
- if (fd < 0)
- return 0;
- //GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,
- // evsel->fdinfo_len);SLOT指的是fd
- //GET_IO_SLOT_AND_CTOR宏将展开成下面这个do{}while(0);
- do
- {
- struct event_map_entry _key, *_ent;
- _key.fd = fd;
- struct event_io_map *_ptr_head = io;
- struct event_map_entry **ptr;
- //哈希表扩容,减少冲突的可能性
- if (!_ptr_head->hth_table
- || _ptr_head->hth_n_entries >= _ptr_head->hth_load_limit)
- {
- event_io_map_HT_GROW(_ptr_head,
- _ptr_head->hth_n_entries + 1);
- }
- #ifdef HT_CACHE_HASH_VALUES
- do{
- (&_key)->map_node.hte_hash = hashsocket((&_key));
- } while(0);
- #endif
- //返回值ptr,是要查找节点的前驱节点的hte_next成员变量的地址.
- //所以返回值肯定不会为NULL,而*ptr就可能为NULL。说明hte_next
- //不指向任何节点。也正由于这个原因,所以即使*ptr 为NULL,但是可以
- //给*ptr赋值。此时,是修改前驱节点的hte_next成员变量的值,使之
- //指向另外一个节点。
- //这里调用_event_io_map_HT_FIND_P原因有二:1.查看该fd是否已经
- //插入过这个哈希表中。2.得到这个fd计算哈希位置。
- ptr = _event_io_map_HT_FIND_P(_ptr_head, (&_key));
- //在event_io_map这个哈希表中查找是否已经存在该fd的event_map_entry了
- //因为同一个fd可以调用event_new多次,然后event_add多次的。
- if (*ptr)
- {
- _ent = *ptr;
- }
- else
- {
- _ent = mm_calloc(1, sizeof(struct event_map_entry) + evsel->fdinfo_len);
- if (EVUTIL_UNLIKELY(_ent == NULL))
- return (-1);
- _ent->fd = fd;
- //调用初始化函数初始化这个evmap_io
- (evmap_io_init)(&_ent->ent.evmap_io);
- #ifdef HT_CACHE_HASH_VALUES
- do
- {
- ent->map_node.hte_hash = (&_key)->map_node.hte_hash;
- }while(0);
- #endif
- _ent->map_node.hte_next = NULL;
- //把这个新建的节点插入到哈希表中。ptr已经包含了哈希位置
- *ptr = _ent;
- ++(io->hth_n_entries);
- }
- //这里是获取该event_map_entry的next和prev指针。因为
- //evmap_io含有next、prev变量。这样在之后就可以把这个
- //event_map_entry连起来。这个外do{}while(0)的功能是
- //为这个fd分配一个event_map_entry,并且插入到现有的哈希
- //表中。同时,这个fd还是结构体event的一部分。而event必须
- //插入到event队列中。
- (ctx) = &_ent->ent.evmap_io;
- } while (0);
- ....
- //ctx->events是一个TAILQ_HEAD。结合之前讲到的TAILQ_QUEUE队列,
- //就可以知道:同一个fd,可能有多个event结构体。这里就把这些结构体连
- //起来。依靠的链表是,event结构体中的ev_io_next。ev_io_next是
- //一个TAILQ_ENTRY,具有前驱和后驱指针。队列头部为event_map_entry
- //结构体中的evmap_io成员的events成员。
- TAILQ_INSERT_TAIL(&ctx->events, ev, ev_io_next);
- return (retval);
- }
GET_IO_SLOT_AND_CTOR宏的作用就是让ctx指向struct event_map_entry结构体中的TAILQ_HEAD。这样就可以使用TAILQ_INSERT_TAIL宏,把ev变量插入到队列中。如果有现成的event_map_entry就直接使用,没有的话就新建一个。