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);
}