hello !大家好呀! 欢迎大家来到我的Linux高性能服务器编程系列之《Linux环境下的事件驱动力量:探索Libevent的高性能I/O架构》,在这篇文章中,你将会学习到Libevent的高性能I/O原理以及应用,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!
希望这篇文章能对你有所帮助,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!(注:这章对于高性能服务器的架构非常重要哟!!!)
目录
一.Libevent简介
二 .Libevent工作流程
三 . 一个简单实例展示流程
四 . Libevent源代码组织结构
一.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 的工作流程可以概括为以下几个步骤:
初始化:
- 调用
event_base_new()
(或旧版本的event_init()
)来创建和初始化一个event_base
实例,这是 Libevent 的核心结构,它代表了一个事件循环。创建事件:
- 使用
event_new()
或event_assign()
创建一个event
实例,并设置其文件描述符、事件类型(如 EV_READ、EV_WRITE)、事件回调函数以及用户数据。添加事件:
- 调用
event_add()
将事件添加到event_base
中,可以指定一个超时时间,这样事件会在指定的时间后被触发。事件循环:
- 调用
event_base_dispatch()
开始事件循环。这个函数会阻塞,直到至少有一个事件准备好。- 在内部,Libevent 会根据操作系统的支持选择合适的事件多路复用机制(如 epoll、select、kqueue)来等待事件。
事件处理:
- 当事件准备好时,Libevent 会调用与之关联的回调函数。
- 回调函数执行完毕后,Libevent 会继续监听其他事件。
修改或删除事件:
- 可以通过
event_del()
从event_base
中删除事件,或者通过event_add()
修改事件的超时时间或重新添加事件。清理:
- 当不再需要事件循环时,可以调用
event_base_free()
来释放event_base
实例,这将清理所有关联的资源。在整个工作流程中,Libevent 提供了高效的事件管理机制,使得开发者可以专注于事件的处理,而无需关心底层的IO多路复用细节。此外,Libevent 还提供了缓冲事件(bufferevent)等高级接口,进一步简化了非阻塞IO的处理。
对应工作流程图:
三 . 一个简单实例展示流程
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( "timeoutn" );
}
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 );
}
代码清单12-1虽然简单,但却基本上描述了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的源代码组织结构可以按照其提供的功能和特性进行分类。以下是根据源代码目录和功能进行的分类总结:
-
核心事件循环:
-
event.c
:实现事件循环的核心逻辑。 -
evmap.c
:管理事件和文件描述符之间的映射。 -
minheap.c
:提供最小堆数据结构,用于定时器管理。
-
-
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支持。
-
-
网络通信:
-
http.c
:HTTP服务器和客户端的实现。 -
http_server.c
:HTTP服务器的实现。 -
http_header.c
:HTTP头的解析。 -
http_parser.c
:HTTP请求/响应的解析。 -
evdns.c
:DNS解析器。
-
-
缓冲区和数据管理:
-
buffer.c
:基础缓冲区管理。 -
evbuffer.c
:扩展的缓冲区管理。 -
bufferevent.c
:缓冲事件,用于非阻塞IO。 -
bufferevent_async.c
:缓冲事件异步支持。 -
bufferevent_filter.c
:缓冲事件过滤器。 -
bufferevent_pair.c
:缓冲事件对。 -
bufferevent_ratelim.c
:缓冲事件速率限制。
-
-
线程和锁:
-
evthread.c
:线程支持。 -
event_tagging.c
:事件标签支持,用于无锁编程。
-
-
信号处理:
-
evsignal.c
:信号处理。 -
signal.c
:信号处理。
-
-
实用工具和辅助功能:
-
evutil.c
:实用工具函数。 -
arc4random.c
:随机数生成器。 -
strlcpy.c
:字符串操作。 -
sys_socket.c
:系统socket支持。 -
sys_event.c
:系统事件支持。
-
-
定时器:
-
timer.c
:定时器实现。 -
wristwatch.c
:高精度定时器支持。
-
-
其他:
-
evrpc.c
:RPC客户端/服务器实现。 -
htmlevents.c
:HTML解析器。
-
这个分类总结展示了Libevent的模块化设计,每个模块负责一个特定的功能,使得Libevent易于扩展和维护。开发者可以根据需要选择和使用不同的模块来构建网络应用程序。
对于I/O库,我们还需要进行优化:
Libevent 是一个高性能的事件通知库,但它也提供了多种选项和策略来帮助开发者进行性能调优。以下是一些可以用来优化 Libevent 性能的策略:
使用合适的 IO 多路复用机制:
- 在 Linux 上,如果可能的话,使用
epoll
而不是select
或poll
。epoll
通常提供更高的性能,尤其是在处理大量文件描述符时。- 在支持
kqueue
的系统上(如 macOS 和 FreeBSD),使用kqueue
可以提供更好的性能。使用边缘触发 (ET) 模式:
- 默认情况下,Libevent 使用水平触发 (LT) 模式。如果你熟悉 ET 模式的工作方式,可以切换到 ET 模式,这可能会提供更高的性能,但需要更仔细地处理事件。
优化事件处理函数:
- 确保 IO 事件的处理函数尽可能高效。避免在事件处理函数中进行阻塞操作或执行耗时较长的任务。
- 如果需要在事件处理函数中执行耗时操作,考虑使用线程池或异步操作。
减少锁的使用:
- 在多线程环境中,减少对共享资源的锁定时间可以提高性能。只在必要时使用锁,并尽量减少锁的粒度。
使用缓冲事件 (bufferevent):
- 使用
bufferevent
可以简化非阻塞 IO 的处理,因为它会自动处理数据的读取和写入,以及相关的 IO 事件。bufferevent
可以减少对事件循环的干扰,因为它会在内部缓冲数据,直到有足够的数据可以处理或发送。优化定时器使用:
- 如果你的应用程序使用了大量的定时器,确保它们被高效地管理和使用。避免不必要的定时器添加和删除操作。
调整事件通知库的配置参数:
- Libevent 允许你调整内部参数,如事件队列大小、超时时间等。根据应用程序的需求调整这些参数可能会提高性能。
使用最新版本的 Libevent:
- 保持 Libevent 库的更新可以确保你获得最新的性能改进和 bug 修复。
监控和性能分析:
- 使用性能分析工具来监控你的应用程序,找出瓶颈所在。这可以帮助你确定哪些部分最需要优化。
避免不必要的内存分配:
- 减少 malloc 和 free 的调用次数,尽量重用内存。Libevent 提供了内存池功能,可以用来减少内存分配的开销。
通过这些策略,你可以优化使用 Libevent 的应用程序的性能。然而,性能调优通常需要根据具体的应用程序和运行环境来进行,因此最好结合具体情况进行调整。
最后,我给出GitHub中的源码仓库链接:https://github.com/libevent/libevent,大家需要看源码的可以自己下载学习哟!!!
好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!