
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.事件处理器和具体事件处理器 事件处理器执行事件对应的业务逻辑。它通常包含一个或多个handleevent回调函数这些回调函数在事件循环中被执行。I/O框架库提供的事件处理器通常是一个接口用户需要继承它来实现自己的事件处理器即具体事件处理器。因此事件处理器中的回调函数一般被声明为虚函数以支持用户的扩展。此外事件处理器一般还提供一个gethandle方法它返回与该事件处理器关联的句柄。那么事件处理器和句柄有什么关系当事件多路分发器检测到有事件发生时它是通过句柄来通知应用程序的。因此我们必须将事件处理器和句柄绑定才能在事件发生时获取到正确的事件处理器。 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的处理。对应工作流程图三 . 一个简单实例展示流程-------------- void signal _ cb( int fd, short event, voidargc ) { struct event _ base* base ( event _ base* ) argc; struct timeval delay { 2, 0 }; printf(Caught an interrupt signal; exiting cleanly in two seconds… );? ?event _ base _ looperit( base, delay ); } void timeout cb( int fd, short event, void* argc ) { printf( timeout ); } 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的源代码组织结构可以按照其提供的功能和特性进行分类。以下是根据源代码目录和功能进行的分类总结1.核心事件循环 *event.c实现事件循环的核心逻辑。 *evmap.c管理事件和文件描述符之间的映射。 *minheap.c提供最小堆数据结构用于定时器管理。2.IO多路复用机制 *epoll.cLinux上的epoll支持。 *select.c传统的select支持。 *poll.cpoll支持。 *kqueue.cBSD系统上的kqueue支持。 *devpoll.cSolaris上的/dev/poll支持。 *evport.cSolaris事件端口支持。 *iocp.cWindows上的IOCP支持。3.网络通信 *http.cHTTP服务器和客户端的实现。 *http_server.cHTTP服务器的实现。 *http_header.cHTTP头的解析。 *http_parser.cHTTP请求/响应的解析。 *evdns.cDNS解析器。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.cRPC客户端/服务器实现。 *htmlevents.cHTML解析器。这个分类总结展示了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大家需要看源码的可以自己下载学习哟 好啦到这里这篇文章就结束啦关于实例代码中我写了很多注释如果大家还有不懂得可以评论区或者私信我都可以哦 感谢大家的阅读我还会持续创造网络编程相关内容的记得点点小爱心和关注哟