Libevent

libevent的event_io_map哈希表

Posted by Liangjf on April 14, 2019

win32有哈希表,linux没有

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
    #define HT_NO_CACHE_HASH_VALUES
    #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

追踪 宏EVMAP_USE_HT 可以得知,基于哈希表的结构来存放event只有在windows平台才有,在linux下是没有的。

在Windows,文件描述符是一个比较大的值,不适合放到event_signal_map结构中。通过哈希(模上一个小的值),就可以变得比较小,这样就可以放到哈希表的数组中了。

遵循POSIX标准的文件描述符是从0开始递增的,一般都不会太大,io事件和信号事件都是使用 event_signal_map ,只不过#define event_io_map event_signal_map,通过一个宏定义来统一命名而已。

event_map_entry

libevent2.1后,原来的 event_io_map 改为了 event_map_entry ,里面的成员变量也拆分成在不同的文件定义了。包括的文件有:ht-internal.h,evmap.h

struct event_map_entry {
    HT_ENTRY(event_map_entry) map_node;
    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;

HT_ENTRY

#ifdef HT_NO_CACHE_HASH_VALUES
#define HT_ENTRY(type)                          \
  struct {                                      \
    struct type *hte_next;                      \
  }
#else
#define HT_ENTRY(type)                          \
  struct {                                      \
    struct type *hte_next;                      \
    unsigned hte_hash;                          \
  }
#endif

那么把各处的成员变量合在一个结构就是:

struct event_map_entry {
    struct {                                      \
        struct type *hte_next;                      \
        unsigned hte_hash;                          \
      }
      
    evutil_socket_t fd;

       union { 
            struct evmap_io evmap_io;
        } ent;
   } **由上面那些结构体配合得到的哈希表结构如下图所示**

event_signal_map

struct evmap_signal {
    struct event_dlist events;
};

信号事件的存放是在一个动态数组中的,并且每个槽连接一条链表,用来存放相同监听相同signal的事件。

由上面那些结构体配合得到的event_signal_map结构如下图所示

哈希表的三个重点

  • 哈希函数
  • 哈希冲突(拉链法)
  • 扩容
  • 缩容

事件加入event_base时

上面都说到event是存放在map中的,那么看下event_add的流程:

  • 1.event_add
  • 2.ent_add_nolock_
  • 3.evmap_io_add_ 或者 evmap_signal_add_
  • 4.GET_IO_SLOT_AND_CTOR

第三步这里详细流程是:

  • 1.判断事件的类型,是读写,关闭,信号事件,非插入,激活,激活更新事件
  • 2.如果是io事件,调用 evmap_io_add_
  • 3.如果是信号事件,调用 evmap_signal_add_

      if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
          !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
          if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
              res = evmap_io_add_(base, ev->ev_fd, ev);
          else if (ev->ev_events & EV_SIGNAL)
              res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
          if (res != -1)
              event_queue_insert_inserted(base, ev);
          if (res == 1) {
              /* evmap says we need to notify the main thread. */
              notify = 1;
              res = 0;
          }
      }
    

追踪源码的时候发现一个觉得需要注意一下的:

if (EVUTIL_UNLIKELY(nread > 0xffff || nwrite > 0xffff || nclose > 0xffff)) {
    event_warnx("Too many events reading or writing on fd %d",
        (int)fd);
    return -1;
}

一个fd可以重复设置事件,但是不能大于 0xffff 个事件。

libevent的五种据结构

  • ngly-linkedists(单链表
  • lists(双向链表)
  • simple queues(单向队列)
  • tail queues(尾队列)
  • circular queues (循环队列)