Libevent中哈希表详解

Libevent的哈希表数据结构如下图所示:


根据fd,通过一个hash算法,在一个数组event_map_entry确定该fd所在的数组位置。因为根据fd的哈希算法,确定的数组位置可能重复,所以在该数组节点处用一个链表存储了不同fd的实际event_map_entry数据数组节点的指针是指向最后一个插入的event_map_entry结构体。

 

下面是针对该数据结构Libevent的具体定义和方法实现:

 

structevent

{

   //活动事件节点指针

         TAILQ_ENTRY(event)ev_active_next;

   //事件节点指针

         TAILQ_ENTRY(event)ev_next;

         //管理超时之用

         union

     {

                   TAILQ_ENTRY(event)ev_next_with_common_timeout;

                   intmin_heap_idx;

         }ev_timeout_pos;

   //文件描述符

         evutil_socket_tev_fd;

   //事件处理核心结构体

         structevent_base *ev_base;   

         union

    {

                   /*用于处理io 事件 */

                   struct

       {

                            TAILQ_ENTRY(event)ev_io_next;

                            structtimeval ev_timeout;

                   }ev_io;

                   /*用于处理信号量事件*/

                   struct{

                            TAILQ_ENTRY(event)ev_signal_next;

                            shortev_ncalls;

                            /*允许在回调中删除 */

                            short*ev_pncalls;

                   }ev_signal;

         }_ev;

   //事件类型

         shortev_events;

   //传递给回调事件的结果值

         shortev_res;

         shortev_flags;

   //事件的优先级

         ev_uint8_tev_pri;   /* smaller numbers are higherpriority */

         ev_uint8_tev_closure;

   //超时时间

         structtimeval ev_timeout;

 

         //事件回调函数

         void(*ev_callback)(evutil_socket_t, short, void *arg);

         void*ev_arg;

};

structevmap_io

{

         structevent_list events;

         ev_uint16_tnread;

         ev_uint16_tnwrite;

};

说明:该结构体存储了某个FD(文件描述符也就是Socket)的所有的读写事件,以及读写次数。

struct event_list 是一个event的TailQueue。

 

structevent_map_entry

{

         HT_ENTRY(event_map_entry)map_node;

         evutil_socket_tfd;

         union

    {

                   structevmap_io evmap_io;

         }ent;

};

说明:map_node是一个指向本结构体的指针,fd为文件描述符,evmap_io是实际存储数据的结构体。

展开后是下面这样:

structevent_map_entry

{

         struct

    {

       struct event_map_entry*hte_next;

    } map_node;

         evutil_socket_tfd;

         union

    {

                   structevmap_io evmap_io;

         }ent;

};

哈希表头定义

#define HT_HEAD(name, type)                                   

struct name

{                                                           

   /* 哈希表自身. */                                        

   struct type **hth_table;                                   

   /* 哈希表长度 */                                        

   unsigned hth_table_length;                                         

   /* 哈希表节点个数 */                    

   unsigned hth_n_entries;                                            

   /*哈希表增长前可以保存的最大个数 */

   unsigned hth_load_limit;                                           

   /* 哈希表长度在素数表中的位置. */            

   int hth_prime_idx;                                                 

  }

  具体定义如下:

  HT_HEAD(event_io_map, event_map_entry);

  展开后定义如下:

structevent_io_map

   struct event_map_entry  **hth_table;

   unsigned hth_table_length;                                        

   unsigned hth_n_entries;                                           

   unsigned hth_load_limit;                                           

   int hth_prime_idx;                                                

}

哈希表所使用的结构体基本定义完毕,但是为了正确使用还需要定义一系列的方法,来实现该哈希表的功能,libevent还是通过宏定义来实现的。

不知道为什么用这么多宏定义,看代码很麻烦。

 

Libevent采用HT_PROTOTYPE和HT_GENERATE两个宏定义封装了对哈希表操作的函数声明和实现。

HT_PROTOTYPE(event_io_map,event_map_entry, map_node, hashsocket, eqsocket)

HT_PROTOTYPE(name,        type,           field,     hashfn,    eqfn)

 

声明了3个函数,实现了几个内联函数,说明如下:

int name##_HT_GROW(structname *ht, unsigned min_capacity);

展开后就是:

int event_io_map_ HT_GROW(struct  event_io_map *ht,unsigned min_capacity);

该函数实现的是哈希表增长的功能,当知道哈希表空间不足时,调用该函数实现空间增长。

void name##_HT_CLEAR(structname *ht);  

展开后就是:

void event_io_map _HT_CLEAR(structevent_io_map *ht); 

该函数实现哈希表清空功能。

int _##name##_HT_REP_IS_BAD(conststruct name *ht);

展开后就是:

int  _ event_io_map_HT_REP_IS_BAD(const structevent_io_map *ht);

该函数是协助调试功能,主要看该哈希表是否正常,如果正常返回0,不正常返回对应的数字表示不同的含义。

内联函数1:

static inline void event_io_map _HT_INIT(structevent_io_map *head);

对哈希表进行初始化。

内联函数2:

static inline struct event_map_entry ** _ event_io_map _HT_FIND_P(struct event_io_map *head, structevent_map_entry *elm) ;

该函数内部又调用了一个宏定义(真麻烦!!!!!!!!!)

_HT_BUCKET(head,field, elm, hashfn);

该宏定义是根据hashfn算法返回elm在该哈希表中的位置。

该位置是event_map_entry类型的双重指针。

然后比较elm和该指针列表内的Socket内的fd是否相同,如果相同的话则返回该指针。

内联函数3:

static inline struct event_map_entry * event_io_map _HT_FIND(const structevent_io_map *head, struct event_map_entry*elm);

还是对上一个函数的调用,只不过返回的是指针类型。

内联函数4:

static inline void event_io_map_HT_INSERT(struct event_io_map *head, struct event_map_entry *elm) ;

将elm插入到哈希表中。

内联函数5:

static inline struct event_map_entry*   event_io_map_HT_REPLACE(struct event_io_map *head, struct event_map_entry *elm)

插入一个要素,如果已经存在,则进行替换。

static inline struct event_map_entry*   event_io_map_HT_REMOVE(struct event_io_map *head, struct event_map_entry *elm);

删除一个要素。

其他方法不常用到。

 

Libevent用如下宏定义实现了哈希方法,和相关变量。

HT_GENERATE(event_io_map,event_map_entry, map_node, hashsocket, eqsocket,0.5, mm_malloc, mm_realloc,mm_free)

define HT_GENERATE(name, type, field, hashfn, eqfn,load, mallocfn, reallocfn, freefn)

 

static unsigned event_io_map_PRIMES[] = {                                   

    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_mapN_PRIMES =    (unsigned)(sizeof(event_io_map _PRIMES)/sizeof(event_io_map_PRIMES[0]));

event_io_map N_PRIMES得到了素数数组的长度。

方法1

int  event_io_map_HT_GROW(struct event_io_map *head, unsigned size)

该方法实现了哈希数组的增长,每次插入数据时都需要判断是否需要增长,如果需要则调用该函数。

方法2

void event_io_map _HT_CLEAR(structevent_io_map *head)

释放数组资源并重新初始化。

 

以上是该哈希表基本的实现函数,下面看如何将一个数据加入到该哈希表中。

int evmap_io_add(structevent_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也是一个宏定义。

//在哈希数组中如果已经存在一个event_map_entry,则返回该结构体内的evmap_io,如果不存在则要//新建一个,并返回该结构内的evmap_io。evmap_io 结构体指针存储在ctx中。

*/

         GET_IO_SLOT_AND_CTOR(ctx, io, fd,evmap_io, evmap_io_init, evsel->fdinfo_len);

    //得到读事件个数

         nread = ctx->nread;

    //得到写事件个数

         nwrite = ctx->nwrite;

    //如果有读或写的事件存在,在临时变量中保存该事件类型。

         if (nread)

                   old |= EV_READ;

         if (nwrite)

                   old |= EV_WRITE;

    //如果是读事件

         if (ev->ev_events & EV_READ)

    {

         //如果是第一次读则纪录该事件类型为读事件,并保存在临时变量res中。

                   if (++nread == 1)

                            res |= EV_READ;

         }

         if (ev->ev_events & EV_WRITE)

    {

          //如果是第一次读则纪录该事件类型为读事件,并保存在临时变量res中。

                   if (++nwrite == 1)

                            res |= EV_WRITE;

         }

    //如果超过65535个度或写事件则返回异常

         if (EVUTIL_UNLIKELY(nread > 0xffff|| nwrite > 0xffff))

    {

                   event_warnx("Too manyevents reading or writing on fd %d",

                       (int)fd);

                   return -1;

         }

         if (EVENT_DEBUG_MODE_IS_ON() &&

            (old_ev = TAILQ_FIRST(&ctx->events)) &&

            (old_ev->ev_events&EV_ET) != (ev->ev_events&EV_ET)) {

                   event_warnx("Tried tomix edge-triggered and non-edge-triggered"

                       " events on fd %d", (int)fd);

                   return -1;

         }

    //如果是第一次增加读或写事件

         if (res) {

                   void *extra = ((char*)ctx) +sizeof(struct evmap_io);

                   /* XXX(niels): we cannot mixedge-triggered and

                    * level-triggered, we should probably asserton

                    * this. */

         //将事件增加到操作系统的监听里

                   if (evsel->add(base,ev->ev_fd,old, (ev->ev_events & EV_ET) | res, extra) == -1)

                            return (-1);

                   retval = 1;

         }

    //记录读写次数

         ctx->nread = (ev_uint16_t) nread;

         ctx->nwrite = (ev_uint16_t) nwrite;

    //将该事件增加到evmap_io内的Tail队列中。

         TAILQ_INSERT_TAIL(&ctx->events,ev, ev_io_next);

         return (retval);

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值