一.Libevent简介
I/O 框架库以库函数的形式,封装了较为底层的系统调用,给应用程序提供了一组更便于使用的接口。这些库函数往往比程序员自己实现的同样功能的函数更合理、更高效,且更健壮。因为它们经受住了真实网络环境下的高压测试,以及时间的考验。
各种I/O框架库的实现原理基本相似,要么以Reactor模式实现, 要么以 Proactor模式实现,要么同时以这两种模式实现。举例来说,基于Reactor模式的I/O框架库包含如下几个组件: 句柄(Handle)、事件多路分发器(EventDemultiplexer)、事件处理器(EventHandler)和具体的事件处理器(ConcreteEventHandler)。
1.句柄 :I/O框架库要处理的对象,即I/O 事件、信号和定时事件,统一称为事件源。一个事件源通常和一个句柄绑定在一起。句柄的作用是,当内核检测到就绪事件时,它将通过句柄来通知应用程序这一事件。在Linux环境下,I/O 事件对应的句柄是文件描述符,信号事件对应的句柄就是信号值。
2.事件多路分发器 :事件的到来是随机的、异步的。我们无法预知程序何时收到一个客户连接请求,又亦或收到一个暂停信号。所以程序需要循环地等待并处理事件,这就是事件循环。在事件循环中,等待事件一般使用I/O 复用技术来实现。I/O 框架库一般将系统支持的各种I/O 复用系统调用封装成统一的接口,称为事件多路分发器。事件多路分发器的demultiplex 方法是等待事件的核心函数,其内部调用的是 select、poll、epoll _ wait等函数。
3.事件处理器和具体事件处理器 :事件处理器执行事件对应的业务逻辑。它通常包含一个或多个hand le event回调函数,这些回调函数在事件循环中被执行。I/O框架库提供的事件处理器通常是一个接口,用户需要继承它来实现自己的事件处理器,即具体事件处理器。因此,事件处理器中的回调函数一般被声明为虚函数,以支持用户的扩展。此外,事件处理器一般还提供一个get handle方法,它返回与该事件处理器关联的句柄。那么,事件处理器和句柄有什么关系?当事件多路分发器检测到有事件发生时,它是通过句柄来通知应用程序的。因此,我们必须将事件处理器和句柄绑定,才能在事件发生时获取到正确的事件处理器。
4. Reactor是I/O 框架库的核心。它提供的几个主要方法是:handle _ events。该方法执行事件循环。它重复如下过程:等待事件,然后依次处理所有就绪事件对应的事件处理器。 register _ handler。该方法调用事件多路分发器的register _ event方法来往事件多路分发器中注册一个事件。remove _ handler。该方法调用事件多路分发器的remove _ event方法来删除事件多路分发器中的一个事件。
对应的框架组件图如下:
二 .Libevent工作流程
Libevent 的工作流程可以概括为以下几个步骤:
1.初始化:
调用 event_base_new()(或旧版本的 event_init())来创建和初始化一个 event_base 实例,这是 Libevent 的核心结构,它代表了一个事件循环。
2.创建事件:
使用 event_new() 或 event_assign() 创建一个 event 实例,并设置其文件描述符、事件类型(如 EV_READ、EV_WRITE)、事件回调函数以及用户数据。
3.添加事件:
调用 event_add() 将事件添加到 event_base 中,可以指定一个超时时间,这样事件会在指定的时间后被触发。
4.事件循环:
-
调用 event_base_dispatch() 开始事件循环。这个函数会阻塞,直到至少有一个事件准备好。
-
在内部,Libevent 会根据操作系统的支持选择合适的事件多路复用机制(如 epoll、select、kqueue)来等待事件。
5.事件处理:
-
当事件准备好时,Libevent 会调用与之关联的回调函数。
-
回调函数执行完毕后,Libevent 会继续监听其他事件。
6.修改或删除事件:
可以通过 event_del() 从 event_base 中删除事件,或者通过 event_add() 修改事件的超时时间或重新添加事件。
7.清理:
当不再需要事件循环时,可以调用 event_base_free() 来释放 event_base 实例,这将清理所有关联的资源。
在整个工作流程中,Libevent 提供了高效的事件管理机制,使得开发者可以专注于事件的处理,而无需关心底层的IO多路复用细节。此外,Libevent 还提供了缓冲事件(bufferevent)等高级接口,进一步简化了非阻塞IO的处理。
对应工作流程图:
需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
三 . 一个简单实例展示流程
void signal _ cb( int fd, short event, void* argc ) { struct event _ base* base = ( event _ base* ) argc; struct timeval delay = { 2, 0 }; printf("Caught an interrupt signal; exiting cleanly in two seconds...\n" ); event _ base _ looperit( base, &delay ); } void timeout cb( int fd, short event, void* argc ) { printf( "timeout\n" ); } int main() { struct event _ base* base = event init(); struct event* signal event= evsignal _ new( base, SIGINT, signal _ cb, base ); event _ add( signal _ event, NULL );timeval tv = { 1, 0 }; struct event* timeout _ event = ovtimer new( base, timeout _ cb, NULL ); event _ add( timeout _ event, &tv);event _ base _ dispatch( base ); event _ free( timeout _ event );event _ free( signal _ event ); event _ base _ free( base ); }
代码虽然简单,但却基本上描述了Libevent库的主要逻辑:
1) 调用event _ init函数创建event _ base 对象。一个event _ base相当于一个Reactor实例。
2)创建具体的事件处理器, 并设置它们所从属的Reactor实例。evsignal _ new和evtimer _ new 分别用于创建信号事件处理器和定时事件处理器,它们是定义在include/event2/event. h文件中的宏:
#define evsignal _ new(b, x, cb, arg)event _ new((b), (x), EV_SIGNAL|EV_PERSIST, (2b) (arg))#define evtimer _ new(b, cb, arg) event _ new((b), -1, 0, (cb), (arg))
可见,它们的统一入口是event _new函数,即用于创建通用事件处理器(EventHandler) 的函数。其定义是:
struct event* event _ new(struct event _ base* base, evutil _ socket _t fd; short events,void (*cb)(evutil _ socket _t, short, void* ), void* arg)
其中, base参数指定新创建的事件处理器从属的Reactor。fd参数指定与该事件处理器关联的句柄,创建I/O事件处理器时,应该给fd参数传递文件描述符值;创建信号事件处理器时,应该给fd参数传递信号值,比如代码清单中的 SIGINT;创建定时事件处理器时,则应该传入fd参数-1 , events参数指定事件类型,其可选值如下:
#define EV_TIMEOUT 0x01 /*定时事件 */ #define EV_READ 0x02 /*可读事件 */ #define EV_WRITE 0x04 /*可写事件 */ #define EV_SIGNAL 0x08 /*信号事件 */ #define EV_PERSIST 0×10 /*永久事件 */ /*边沿触发事件,需要I/O复用系统调用支持,比如 epoll */ #define EV_ET 0x20
四 . Libevent源代码组织结构
Libevent的源代码组织结构可以按照其提供的功能和特性进行分类。以下是根据源代码目录和功能进行的分类总结:
1.核心事件循环:
-
event.c:实现事件循环的核心逻辑。
-
evmap.c:管理事件和文件描述符之间的映射。
-
minheap.c:提供最小堆数据结构,用于定时器管理。
2.IO多路复用机制:
-
epoll.c:Linux上的epoll支持。
-
select.c:传统的select支持。
-
poll.c:poll支持。
-
kqueue.c:BSD系统上的kqueue支持。
-
devpoll.c:Solaris上的/dev/poll支持。
-
evport.c:Solaris事件端口支持。
-
iocp.c:Windows上的IOCP支持。
3.网络通信:
-
http.c:HTTP服务器和客户端的实现。
-
http_server.c:HTTP服务器的实现。
-
http_header.c:HTTP头的解析。
-
http_parser.c:HTTP请求/响应的解析。
-
evdns.c:DNS解析器。
4.缓冲区和数据管理:
-
buffer.c:基础缓冲区管理。
-
evbuffer.c:扩展的缓冲区管理。
-
bufferevent.c:缓冲事件,用于非阻塞IO。
-
bufferevent_async.c:缓冲事件异步支持。
-
bufferevent_filter.c:缓冲事件过滤器。
-
bufferevent_pair.c:缓冲事件对。
-
bufferevent_ratelim.c:缓冲事件速率限制。
5.线程和锁:
-
evthread.c:线程支持。
-
event_tagging.c:事件标签支持,用于无锁编程。
6.信号处理:
-
evsignal.c:信号处理。
-
signal.c:信号处理。
7.实用工具和辅助功能:
-
evutil.c:实用工具函数。
-
arc4random.c:随机数生成器。
-
strlcpy.c:字符串操作。
-
sys_socket.c:系统socket支持。
-
sys_event.c:系统事件支持。
8.定时器:
-
timer.c:定时器实现。
-
wristwatch.c:高精度定时器支持。
9.其他:
-
evrpc.c:RPC客户端/服务器实现。
-
htmlevents.c:HTML解析器。
这个分类总结展示了Libevent的模块化设计,每个模块负责一个特定的功能,使得Libevent易于扩展和维护。开发者可以根据需要选择和使用不同的模块来构建网络应用程序。
对于I/O库,我们还需要进行优化:
Libevent 是一个高性能的事件通知库,但它也提供了多种选项和策略来帮助开发者进行性能调优。以下是一些可以用来优化 Libevent 性能的策略:
1.使用合适的 IO 多路复用机制:
-
在 Linux 上,如果可能的话,使用 epoll 而不是 select 或 poll。epoll 通常提供更高的性能,尤其是在处理大量文件描述符时。
-
在支持 kqueue 的系统上(如 macOS 和 FreeBSD),使用 kqueue 可以提供更好的性能。
2.使用边缘触发 (ET) 模式:
默认情况下,Libevent 使用水平触发 (LT) 模式。如果你熟悉 ET 模式的工作方式,可以切换到 ET 模式,这可能会提供更高的性能,但需要更仔细地处理事件。
3.优化事件处理函数:
-
确保 IO 事件的处理函数尽可能高效。避免在事件处理函数中进行阻塞操作或执行耗时较长的任务。
-
如果需要在事件处理函数中执行耗时操作,考虑使用线程池或异步操作。
4.减少锁的使用:
在多线程环境中,减少对共享资源的锁定时间可以提高性能。只在必要时使用锁,并尽量减少锁的粒度。
5.使用缓冲事件 (bufferevent):
-
使用 bufferevent 可以简化非阻塞 IO 的处理,因为它会自动处理数据的读取和写入,以及相关的 IO 事件。
-
bufferevent 可以减少对事件循环的干扰,因为它会在内部缓冲数据,直到有足够的数据可以处理或发送。
6.优化定时器使用:
如果你的应用程序使用了大量的定时器,确保它们被高效地管理和使用。避免不必要的定时器添加和删除操作。
7.调整事件通知库的配置参数:
Libevent 允许你调整内部参数,如事件队列大小、超时时间等。根据应用程序的需求调整这些参数可能会提高性能。
8.使用最新版本的 Libevent:
保持 Libevent 库的更新可以确保你获得最新的性能改进和 bug 修复。
9.监控和性能分析:
使用性能分析工具来监控你的应用程序,找出瓶颈所在。这可以帮助你确定哪些部分最需要优化。
10.避免不必要的内存分配:
减少 malloc 和 free 的调用次数,尽量重用内存。Libevent 提供了内存池功能,可以用来减少内存分配的开销。
通过这些策略,你可以优化使用 Libevent 的应用程序的性能。然而,性能调优通常需要根据具体的应用程序和运行环境来进行,因此最好结合具体情况进行调整。
GitHub源码链接:https://github.com/libevent/libevent,大家需要看源码的可以自己下载学习哟!!!