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

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  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/670895.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java中的字符串(String)常量池理解

下面创建String对象的方式一样吗? 上述程序创建对象类似,为什么s1和s2引用对象一样,但是s3和s4不一样呢? 在java程序中,许多基本类型的字面常量会经常用到,例如2,3.11,“hyy”等。为了提升程序…

一文读懂Vue生命周期(Vue2)

一文读懂Vue生命周期(Vue2) 目录 一文读懂Vue生命周期(Vue2)1 前言2 Vue生命周期2.1 基本生命周期2.1.1 8个生命周期2.1.2 案例 2.2 组件生命周期2.2.1 父子生命周期2.2.2 案例 2.3 keep-alive生命周期2.3.1 案例 2.4 其他 3 总结…

OpenHarmony实战开发-应用侧调用前端页面函数

应用侧可以通过runJavaScript()方法调用前端页面的JavaScript相关函数。 在下面的示例中&#xff0c;点击应用侧的“runJavaScript”按钮时&#xff0c;来触发前端页面的htmlTest()方法。 前端页面代码。 <!-- index.html --> <!DOCTYPE html> <html> <…

迭代器解释(C++)

一、什么是迭代器 为了提高C编程的效率&#xff0c;STL&#xff08;Standard Template Library&#xff09;中提供了许多容器&#xff0c;包括vector、list、map、set等。然而有些容器&#xff08;vector&#xff09;可以通过下标索引的方式访问容器里面的数据&#xff0c;但是…

php基础知识快速入门

一、PHP基本知识 1、php介绍&#xff1a; php是一种创建动态交互性的强有力的服务器脚本语言&#xff0c;PHP是开源免费的&#xff0c;并且使用广泛。PHP是解释性语言&#xff0c;按顺序从上往下执行&#xff0c;无需编译&#xff0c;直接运行。PHP脚本在服务器上运行。 2、ph…

【STM32嵌入式系统设计与开发】——18StaticNixite(静态数码管应用)

这里写目录标题 STM32资料包&#xff1a; 百度网盘下载链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1mWx9Asaipk-2z9HY17wYXQ?pwd8888 提取码&#xff1a;88881、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;主函数头文件函数&#x…

Vue 介绍

【1】前端发展史 前端的发展史可简述为&#xff1a; 从最初的静态页面编写&#xff0c;依赖后端模板渲染逐步演化为通过JavaScript&#xff08;特别是Ajax技术&#xff09;实现前后端分离&#xff0c;使得前端能够独立地加载数据和渲染页面随后&#xff0c;Angular、React、Vu…

CTF(Web)中关于执行读取文件命令的相关知识与绕过技巧

在我遇到的题目中&#xff0c;想要读取文件必然是要执行cat /flag这个命令&#xff0c;但是题目当然不会这么轻松。让你直接cat出来&#xff0c;必然会有各种各样的滤过条件&#xff0c;你要做的就是尝试各种方法在cat /flag的基础上进行各种操作构建出最终的payload。 下面我…

Vite构造Vue3

环境安装 node.js安装-CSDN博客 初始化Vue项目安装脚手架_vue init webpack安装脚手架-CSDN博客 选择Vue框架 &#xff0c;项目名称可以自定义&#xff0c;我使用默认的 vite-project 选择JS 进入项目安装依赖 安装路由

Leetcode编程练习

面试题-消失的数字 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void reverse(vector<int>& nums, int start, int end) {while (start < end) {swap(nums[start], nums[end]);start 1;end - 1;}}void rotate(vector<int>& …

AI预测体彩排3第3套算法实战化赚米验证第2弹2024年5月6日第2次测试

由于今天白天事情比较多&#xff0c;回来比较晚了&#xff0c;趁着还未开奖&#xff0c;赶紧把预测结果发出来吧~今天是第2次测试~ 2024年5月6日排列3预测结果 6-7码定位方案如下&#xff1a; 百位&#xff1a;2、3、1、5、0、6 十位&#xff1a;4、3、6、8、0、9 个位&#xf…

自动驾驶主流芯片及平台架构(二)特斯拉自动驾驶芯片平台介绍

早期 对外采购mobileye EyeQ3 芯片摄像头半集成方案&#xff0c;主要是为了满足快速量产需求&#xff0c;且受制于研发资金不足限制&#xff1b; 中期 采用高算力NVIDIA 芯片平台其他摄像头供应商的特斯拉内部集成方案&#xff0c;mobileye开发节奏无法紧跟特斯拉需求&#xff…