Linux环境下的事件驱动力量:探索Libevent的高性能I/O架构

发布于:2024-05-07 ⋅ 阅读:(40) ⋅ 点赞:(0)

hello !大家好呀! 欢迎大家来到我的Linux高性能服务器编程系列之《Linux环境下的事件驱动力量:探索Libevent的高性能I/O架构》,在这篇文章中,你将会学习到Libevent的高性能I/O原理以及应用,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!

希望这篇文章能对你有所帮助9fe07955741149f3aabeb4f503cab15a.png,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!1a2b6b564fe64bee9090c1ca15a449e3.png(注:这章对于高性能服务器的架构非常重要哟!!!)

03d6d5d7168e4ccb946ff0532d6eb8b9.gif         

 

 

目录

 一.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 的工作流程可以概括为以下几个步骤:

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

 代码清单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.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 或 pollepoll 通常提供更高的性能,尤其是在处理大量文件描述符时。
    • 在支持 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,大家需要看源码的可以自己下载学习哟!!!

      好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦4d7d9707063b4d9c90ac2bca034b5705.png!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!2cd0d6ee4ef84605933ed7c04d71cfef.jpeg