热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Libevent初探及其设计模式

libevent介绍Libevent是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:1、事件驱动(event-d

libevent介绍

Libevent是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:
1、事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;
2、源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;
3、支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;
4、支持 I/O,定时器和信号等事件;注册事件优先级。Libevent 已经被广泛的应用,作为底层的网络库;

libevent源代码文件组织

1、前言

详细分析源代码之前,如果能对其代码文件的基本结构有个大概的认识和分类,对于代码的分析将是大有裨益的。本节内容不多,我想并不是说它不重要!

2、源代码组织结构

Libevent的源代码虽然都在一层文件夹下面,但是其代码分类还是相当清晰的,主要可分为头文件、内部使用的头文件、辅助功能函数、日志、libevent框架、对系统I/O多路复用机制的封装、信号管理、定时事件管理、缓冲区管理、基本数据结构和基于libevent的两个实用库等几个部分,有些部分可能就是一个源文件。
源代码中的test部分就不在我们关注的范畴了。
1)头文件
主要就是event.h:事件宏定义、接口函数声明,主要结构体event的声明;
2)内部头文件
xxx-internal.h:内部数据结构和函数,对外不可见,以达到信息隐藏的目的;
3)libevent框架
event.c:event整体框架的代码实现;
4)对系统I/O多路复用机制的封装
epoll.c:对epoll的封装;
select.c:对select的封装;
devpoll.c:对dev/poll的封装;
kqueue.c:对kqueue的封装;
5)定时事件管理
min-heap.h:其实就是一个以时间作为key的小根堆结构;
6)信号管理
signal.c:对信号事件的处理;
7)辅助功能函数
evutil.h 和evutil.c:一些辅助功能函数,包括创建socket pair和一些时间操作函数:加、减和比较等。
8)日志
log.h和log.c:log日志函数
9)缓冲区管理
evbuffer.c和buffer.c:libevent对缓冲区的封装;
10)基本数据结构
compat/sys下的两个源文件:queue.h是libevent基本数据结构的实现,包括链表,双向链表,队列等;_libevent_time.h:一些用于时间操作的结构体定义、函数和宏定义;
11)实用网络库
http和evdns:是基于libevent实现的http服务器和异步dns查询库;

Libevent事件处理流程

这里写图片描述

基本使用场景和事件流程:
当应用程序向libevent 注册一个事件后,libevent 内部是怎么样进行处理的呢?下面的图就给出了这一基本流程。

  • 1、首先应用程序准备并初始化event,设置好事件类型和回调函数;这对应于event_set()、event_assign()event_base_set()两个函数;
  • 2、向libevent 添加该事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于Signal和I/O事件,libevent将其放入到等待链表(wait list)中,这是一个双向链表结构;
  • 3、程序调用event_base_dispatch()系列函数进入无限循环,等待事件发生,以epoll函数为例;每次循环前libevent会检查定时事件的最小超时时间tv,根据tv设置epoll的最大等待时间,以便于后面及时处理超时事件;当epoll_wait()返回后,首先检查超时事件,然后检查I/O事件;Libevent将所有的就绪事件,放入到激活链表中;然后对激活链表中的事件,调用事件的回调函数执行事件处理。

Libevent设计模式

5大IO模型

1、 同步阻塞IO(Blocking IO)
即传统的IO模型。当用户进程向系统发起read操作时,首先需要在内核中数据准备和内核态到用户进程的数据拷贝。当两个步骤都完成后,才会返回read结果状态,才能执行后续的数据处理操作。这种read会阻塞程序,现在大部分都不使用这种模式了。

{read(socket, buffer);process(buffer);
}

2、 同步非阻塞IO(Non-blocking IO)
默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。当用户进程向系统发起read操作时,立即返回,但此时并没有读取到数据。用户线程需要不断地发起read请求,并根据返回的结果是否完成状态(EWOULDBLOCK),来确定是否完成read操作。

while(read(socket, buffer) == SUCCESS) {process(buffer);
}

在非阻塞式IO中,用户进程需要不断的主动询问数据准备好了没有,需要消耗过多的CPU 资源。
3、 IO多路复用(IO Multiplexing)
即经典的Reactor设计模式,有时也称为异步阻塞IO,Linux中的epoll都是这种模型。以下以select为例进行说明。使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

{select(socket);while(1) {ready_sockets = select();for(socket in ready_sockets) {if(can_read(socket)) {read(socket, buffer);process(buffer);}}}
}

4、 异步IO(Asynchronous IO)
即Proactor设计模式,也称为异步非阻塞IO。用户进程发起read操作之后,立刻就可以开始去做其它的事。而内核在接收到asynchronous read之后,内核会进行数据准备和数据拷贝至用户内存,当这两个步骤都完成后,内核会给用户进程发送一个signal,通知read操作完成。这一过程不会对用户进程产生任何block。

相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)
下面用一张图区别四种模型的区别:
这里写图片描述

Reactor模式

Reactor模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:
1、响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
2、编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程进程的切换开销;
3、可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
4、可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性。

下图描述了Reactor模式的框架,主要包括:事件源、框架部分(Reactor)、事件多路分发机制(event demultiplexing)、事件处理程序(event handler)。

这里写图片描述

1、事件源:Linux 上是文件描述符, Windows 上就是 Socket 或者 Handle 了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,比如 I/O 事件。

在libevent中有三种类型的事件:定时器事件(time event)、信号事件(signal event)和I/O事件。

2、事件多路分发机制(event demultiplexing)
需要使用底层提供的多路复用机制,如evport, select , poll, epoll, kqueue, devpoll. 用户进程首先在event demultiplexing上注册事件,采用合适的多路复用机制检测事件,当事件发生时,event demultiplexing发出通知“在已经注册的事件集中,一个或多个事件已经就绪“,程序收到通知后对事件进行处理。

1)libevent中对多路复用机制进行了封装,使得根据操作系统,可以选择最高效的IO机制。

2)事件注册:
首先,对event进行初始化,并将event与event_base(可以理解为事件库)关联起来,如下:

event_new(struct event_base base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void ), void *arg)
1
其中cb表示事件处理函数,也即回调函数,需要用户实现。

然后,将事件添加到事件库,此时event的状态为pending:
int event_add(struct event *ev, const struct timeval *tv);

3)事件触发:
在事件加入event_base后,选择合适的多路复用机制遍历事件队列,将状态为激活(active)的事件插入到激活队列中,从高到低优先级遍历激活event优先级数组。对于激活的event,调用event_queue_remove将之从激活队列中删除掉。然后再对这个event调用其回调函数。


推荐阅读
  • MySQL性能优化与调参指南【数据库管理】
    本文详细探讨了MySQL数据库的性能优化与参数调整技巧,旨在帮助数据库管理员和开发人员提升系统的运行效率。内容涵盖索引优化、查询优化、配置参数调整等方面,结合实际案例进行深入分析,提供实用的操作建议。此外,还介绍了常见的性能监控工具和方法,助力读者全面掌握MySQL性能优化的核心技能。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 本文详细介绍了批处理技术的基本概念及其在实际应用中的重要性。首先,对简单的批处理内部命令进行了概述,重点讲解了Echo命令的功能,包括如何打开或关闭回显功能以及显示消息。如果没有指定任何参数,Echo命令会显示当前的回显设置。此外,文章还探讨了批处理技术在自动化任务执行、系统管理等领域的广泛应用,为读者提供了丰富的实践案例和技术指导。 ... [详细]
  • 在使用 SQL Server 时,连接故障是用户最常见的问题之一。通常,连接 SQL Server 的方法有两种:一种是通过 SQL Server 自带的客户端工具,例如 SQL Server Management Studio;另一种是通过第三方应用程序或开发工具进行连接。本文将详细分析导致连接故障的常见原因,并提供相应的解决策略,帮助用户有效排除连接问题。 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 普通树(每个节点可以有任意数量的子节点)级序遍历 ... [详细]
  • 本文详细介绍了如何使用Python的多进程技术来高效地分块读取超大文件,并将其输出为多个文件。通过这种方式,可以显著提高读取速度和处理效率。 ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 第二十五天接口、多态
    1.java是面向对象的语言。设计模式:接口接口类是从java里衍生出来的,不是python原生支持的主要用于继承里多继承抽象类是python原生支持的主要用于继承里的单继承但是接 ... [详细]
  • 深入理解Linux网络编程:UDP协议实战解析
    深入理解Linux网络编程:UDP协议实战解析 ... [详细]
  • 使用Boost.Asio进行异步数据处理的应用程序主要依赖于两个核心概念:I/O服务和I/O对象。I/O服务抽象了操作系统接口,使得异步操作能够高效地执行。I/O对象则代表了具体的网络资源,如套接字和文件描述符,通过这些对象可以实现数据的读写操作。本文详细介绍了这两个概念在Boost.Asio中的应用及其在网络编程中的重要性。 ... [详细]
author-avatar
念中怡名哲盈_452
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有