libevent源码笔记(三) 主要结构

本文详细解析了libevent的核心结构,包括Reactor实现的event_base结构体、I/O复用机制封装的eventop结构体以及事件处理器event结构体。探讨了它们在事件驱动编程中的作用与实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3. 主要结构体

 3.1   event_base

结构体event_base是libevent的Reactor,可以说是libevent的核心。它定义在event-internal.h中,代码及作用用下所示:

//定于Reactor,位于event-internal.h
struct event_base {
	//Reactor初始化时采用的I/O复用机制
    const struct eventop *evsel; 
	//指向I/O复用机制真正存储的数据,通过evsel成员的init函数来初始化
    void *evbase;
	//事件变化队列,当一个文件描述符上注册的事件被多次修改,则可以使用缓冲来避免重复的系统调用.仅能用于
	//时间复杂度为O(1)的I/O复用技术(比如epoll_ctl)
    struct event_changelist changelist;
	//指向信号的后端处理机制
    const struct eventop *evsigsel;
	//信号事件处理器使用的数据结构,封装了一个由socketpair创建的管道,用于信号处理函数和多路分发器之间的通信
    struct evsig_info sig;
	//添加到本event_base的虚拟事件的数量?虚拟事件是什么
    int virtual_event_count;
	//添加到本event_base的所有事件的数量
    int event_count;
	//本event_base激活事件的数量
    int event_count_active;
	//是否执行完活动事件队列上剩余的任务之后就退出事件循环
    int event_gotterm;
	//是否立即退出事件循环,而不管是否还有任务需要处理
    int event_break;
	//是否立即启动一个新的事件循环
    int event_continue; 
	//当前event_base正在处理的活动事件队列的优先级
    int event_running_priority;
	//事件循环是否已经启动
    int running_loop;
	//活动事件队列数组,索引值越小,优先级越高。高优先级的活动事件队列中的事件处理器优先处理
    struct event_list *activequeues;
	//活动事件队列数组的大小,即该event_base一共有多少个不同优先级的活动事件队列
    int nactivequeues;
	//通用定时器队列
    struct common_timeout_list **common_timeout_queues;
	//通用定时器队列中元素个数
    int n_common_timeouts;
	//通用定时器队列所占空间总大小
    int n_common_timeouts_allocated;
	//存放延迟回调函数的链表。事件循环每次成功处理完一个活动事件队列中的所有事件之后,就调用一次延迟回调函数
    struct deferred_cb_queue defer_queue;
	//文件描述符和I/O事件关系映射表
    struct event_io_map io;
	//信号值和信号事件之间的映射关系表
    struct event_signal_map sigmap;
	//注册时间队列,存放I/O事件处理器和信号事件处理器
    struct event_list eventqueue;
	//记录事件循环上一次检测的时间点
    struct timeval event_tv;
	//最小堆、时间堆
    struct min_heap timeheap;
	//存储时间点,避免太频繁调用gettimeofday/clock_gettime
    struct timeval tv_cache;
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
	//clock_gettime和gettimeofday的差值
    struct timeval tv_clock_diff;
	//最后一次更新tv_clock_diff的时间
    time_t last_updated_clock_diff;
#endif
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
	//运行当前事件循环的线程号
    unsigned long th_owner_id;
	//event_base独占锁
    void *th_base_lock;
	//当前事件循环正在执行哪个事件处理器的回调函数
    struct event *current_event;
	//条件变量,用于唤醒正在等待事件处理完毕的线程
    void *current_event_cond;
	//等待被唤醒的线程数
    int current_event_waiters;
#endif
#ifdef WIN32
    struct event_iocp_port *iocp;
#endif
	//event_base的一些配置参数
    enum event_base_config_flag flags;
	//以下用以给工作线程唤醒主线程
	//若主线程处于唤醒状态,设为true
    int is_notify_pending;
	//唤醒主线程的双向管道
    evutil_socket_t th_notify_fd[2];
	//通知事件
    struct event th_notify;
	//其它线程唤醒主线程时调用的方法
    int (*th_notify_fn)(struct event_base *base);
};

3.2 eventop

eventop结构体封装了I/O复用机制必要的一些操作,比如注册事件,等待事件等,它为event_base支持的所有后端复用机制提供了一个统一的接口。eventop定义在event-internal.h中,具体如下:

struct eventop {
	//I/O复用名称
    const char *name;
	//为一个事件循环初始化使用该I/O复用机制所需的所有资源,返回指向这些数据的指针,
	//存储在event_base.evbase中,失败返回null
    void *(*init)(struct event_base *);
	//注册事件,event_base表示事件循环,fd表示事件源,old表示该事件源上已注册的方法events表示本次要注册的方法
	//fdinfo表示evmap中一个与事件源相关的结构,长度由fdinfo_len定义,当该事件源是第一次添加时,fdinfo_len为0
	//函数成功返回0,失败返回-1
    int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	//删除一个已注册的事件
    int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
   	// 检测所有注册事件是否已就绪,为每个已就绪事件的event_active方法
    int (*dispatch)(struct event_base *, struct timeval *);
	//释放相关内存
    void (*dealloc)(struct event_base *);
	//标识程序执行fork后是否需要重新初始化
    int need_reinit;
	//I/O技术支持的一些特性,可选如下三个值的按位或:EV_FEATURE_ET(边沿触发事件),EV_FEATURE_01(事件监测算法是O(1))和
	//EV_FEATURE_FDS(不仅能监听socket上的事件,还能监听其他类型的文件描述符上的事件)
	enum event_method_feature features;
	//有的I/O复用机制需要为每个I/O事件队列和信号事件队列分配额外的内存,以避免同一个文件描述符被重复插入I/O复用机制的事件表中。
	//evmap_io_add(或evmap_io_del)函数在调用eventop的add(或del)方法时,将这段内存的起始地址作为第5个参数传递给add(或del)方法,
	//下面这个成员指定了这段内存的长度
    size_t fdinfo_len;
};

对于不同的I/O复用机制来说,只需要定义一个eventop对象,初始化对应的成员即可。当event_base初始化的时候,会结合当前系统环境和I/O复用优先级选择一种I/O复用技术。
3.3 event

接下来看libevent的事件处理器结构struct event。struct event定义在include/event2/event_struct.h中,具体如下所示:

<span style="white-space:pre">	</span>struct event {
	//活动事件队列,所有被激活的事件处理器通过该成员串联成一个尾队列
	//活动事件队列不止一个,不同优先级的事件处理器被激活后将插入不同的活动事件队列中
	//在事件循环中,Reactor将按照优先级从高到低遍历所有活动事件队列,并以此处理其中的事件处理器
	TAILQ_ENTRY(event) ev_active_next;
	//注册事件队列 所有已注册的事件处理器通过该成员串联成一个尾队列
	TAILQ_ENTRY(event) ev_next;
	//仅用于定时器。对于通用定时器,存储在尾队列中,ev_next_*指定尾队列中所处位置
	//其它定时器存在于时间堆中,min_heap_idx指定在时间堆中的位置
	//可通过event.c中的is_common_timeout函数判断是否为通用定时器
	union{
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		int min_heap_idx;
	}ev_timeout_pos;
	//事件处理器从属的event_base实例
	struct event_base *ev_base;
	//事件处理器从属的事件队列
	union{
		// I/O事件队列
		struct{
			//所有具有相同文件描述符的I/O事件处理器通过本成员串联成尾队列
			TAILQ_ENTRY(event) ev_io_next;
			//超时时间,过了该时间后该I/O事件失效
			struct timeval ev_timeout;
		}ev_io;
		// 信号事件队列
		struct{
			//所有具有相同信号值的信号处理事件处理器通过ev.ev_signal成员串联成尾队列
			TAILQ_ENTRY(event) ev_signal_next;
			//指定信号值事件发生时,执行多少次对应的回调函数
			short ev_ncalls;
			//指向ev_ncalls或NULL,用于回调函数删除或修改
			short *ev_pncalls;
		}ev_signal;
	}_ev;
	//注册的事件类型按位或(读写和信号不能同时设置),包括如下事件
	/* #define EV_TIMEOUT 0x01 定时事件
	   #define EV_READ 0x02 可读事件
	   #define EV_WRITE 0x04 可写事件
	   #define EV_SIGNAL 0x08 信号事件
	   #define EV_PERSIST 0x10 永久事件
	   #define EV_ET 边沿触发事件,需要I/O复用系统调用支持
	   */
	short ev_events;
	//记录当前激活事件的类型
	short ev_res;
	//事件标志
	/*	#define EVLIST_TIMEOUT 0x01 事件处理器从属于通用定时器队列或时间堆
		#define EVLIST_INSERTED 0x02 事件处理器从属于注册事件队列
		#define EVLIST_ACTIVE 0x08 事件处理器从属于活动事件队列
		#define EVLIST_INTERNAL 0x10 内部使用
		#define EVLIST_INIT 0x80 事件处理器已被初始化
		#define EVLIST_ALL (0xf000 | 0x9f) 定义所有标志
	   */
	short ev_flags;
	//指定时间优先级,值越小优先级越高
	ev_uint8 ev_pri;
	//指定event_base执行事件处理器的回调函数的行为
	/*	#define EV_CLOSURE_NONE 0 默认行为
		#define EV_CLOSURE_SIGNAL 1 执行信号事件处理器的函数时
		#define EV_CLOSURE_PERSIST 2 执行回调函数后,再次将事件加入注册事件队列中
		*/
	e_uint8 ev_closure;
	//仅对定时器有效,指定定时器的超时值
	struct timeval ev_timeout;
	//事件回调函数,由event_base调用。入参分别为ev_fd, ev_res和ev_arg
	void (*ev_callback)(evutil_socket_t, short, void *arg);
	//回调函数参数
	void *ev_arg;
};


event事件处理器封装了句柄、事件类型、回调函数以及其它必要的标志和数据。在event结构体中,涉及到的一个结构体是尾队列,一般通过以下宏来定义

#define TAILQ_ENTRY(type) \
struct {     \
     struct type *tqe_next;     \     //指向下个元素
     struct type **tqe_prev;     \     //前一个元素的next指针的地址
}
#define TAILQ_HEAD(name, type)     \
struct name{     \
     struct type *tqh_first;     \     //指向第一个元素
     struct type **tqh_last;     \     //指向最后一个元素的next指针的地址
}
TAILQ_ENTRY表示队列的一个节点,TAILQ_HEAD表示该队列的头,该宏定义可以在event_struct.h和compat/sys/queue.h中找到





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值