如何在linux下实现event事件机制

如何在linux下实现event事件机制,第1张

一、Libevent简介

libevent是一个基于事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。

特点:

事件驱动,高性能

轻量级,专注于网络,不如ACE那么臃肿庞大,只提供了简单的网络API的封装,线程池,内存池,递归锁等均需要自己实现;

开放源码,代码相当精炼、易读;

跨平台,支持Windows、Linux、BSD和Mac OS;

支持多种I/O多路复用技术(epoll、poll、dev/poll、select和kqueue等),在不同的 *** 作系统下,做了多路复用模型的抽象,可以选择使用不同的模型,通过事件函数提供服务;

支持I/O,定时器和信号等事件;

采用Reactor模式;

二、源码组织结构

Libevent 的源代码虽然都在一层文件夹下面,但是其代码分类还是相当清晰的,主要可分为头文件、内部使用的头文件、辅助功能函数、日志、libevent框架、对系 统I/O多路复用机制的封装、信号管理、定时事件管理、缓冲区管理、基本数据结构和基于libevent的两个实用库等几个部分,有些部分可能就是一个源文件。

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查询库;

        static struct input_dev* sif_input_dev = NULL

        sif_input_dev  = input_allocate_device()

        sif_input_dev->name = "sif_event"

        set_bit(EV_MSC,  sif_input_dev->evbit)

        set_bit(MSC_RAW, sif_input_dev->mscbit)

        input_register_device(sif_input_dev)

        input_event(sif_input_dev, EV_MSC, MSC_RAW, 1)

        input_sync(sif_input_dev)

        open(INPUT_DEV, O_RDONLY)

        read(fd, &event, sizeof(event))

        printf("type:%d, code:%d, value:%d\n", event.type, event.code, event.value)

libevent是一个轻量级的开源高性能网络库,基于事件驱动,跨平台支持WIN linux Mac 支持多种IO多路复用技术,支持 IO 定时器和信号等事件的统一调度,支持注册事件的优先级。memcache 使用libevent作为底层网络库。

Reactor 模式:

我们普通的函数调用 ,是程序调用某函数 ,函数执行中一直等待该函数执行完之后再继续执行下面的代码。Reactor 模式是一种事件驱动机制。和普通的函数调用不同的是这里的应用程序不是主动的调用某个API函数完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor,如果相应的事件发生,Reactor将主动调用应用 注册的接口,这些函数是回调函数。开始用户会在相应的event中设置回调函数和相应监听句柄并由libevent中的Reactor实例进行管理。

采用Reactor模式是编写高性能网络服务器的必备技术之一:

优点:响应快,不会因为某个同步事件所阻塞,因为采用的是回调函数执行,虽然Reactor本身是同步的。

采用Reactor框架本身与具体事件的处理没有关系,只负责处理与用户的交互,具有很高复用性。

可以扩展多个Reactor实例来实现多CPU的资源利用

因为采用了阻塞的select epoll等IO复用函数进行阻塞监听批量的句柄,所以在事件到来时事件的处理逻辑,也就是回调函数不会阻塞住,而是非阻塞的执行。

应用场景:

1.初始化libevent的实例也就是struct event_base结构体也就是对应的Reactor模型在libevent中的实体

struct event_base *base = event_init()

2.用户初始化所要注册的事件 根据不同的事件,网络中主要包括 定时事件,IO事件,信号事件,libevent中使用宏方便用户根据不同的事件调用与事件名称相匹配的函数,但是内部全部都是调用一个借口event_set(),参数中对于所有时间都会有一个函数指针用于用户注册回调函数,一个句柄(对于IO事件就是文件描述符,信号就是信号的编号,对于定时事件不用设置)

3.将事件本身的基本信息设置好之后要和Reactor的实例也就是和某一个event_base 进行联系,因为可能存在多个event_base 实例

4.基本信息设置完成之后,调用event_add 函数将事件通过Reactor实例也就是struct_base的统一接口找到性能最高的IO复用函数注册到其中,包括设置超时时间。对于定时事件,libevent使用一个小根堆管理,key为超时时间,对于IO和信号事件,将该事件放到等待双向链表中,

5.进入无限循环等待就绪事件,以epoll为例,每次循环前,libevent都会检查定时事件中最小的超时时间tv,根据tv设置epoll的最大等待时间,以便后面及时处理超时事件,当epoll超时返回后就将超时事件添加到就绪队列如果是正确返回就不用添加超时事件,之后同样直接依次遍历就绪队列执行相应的回调函数处理逻辑。此处可以看出是同步处理逻辑的。(IO事件已经在epoll_wait中添加进了就绪队列了)

IO和timer事件的统一:

因为系统提供的IO机制像select或者epoll_wait 都允许程序制定一个最大的等待时间,也称作最大超时时间timeout,即使没有IO事件发生,也能保证能在timeout时间到达时候返回。

根据所有timer事件的最小超时事件来设置系统IO的timeout时间,当系统IO返回时候再激活所有继续的timer事件就可以了,这样就能将timer事件完美的融合到系统的IO机制中去了。这是Reactor 和Proactor模式中处理Timer事件最经典的方法了。

libevent支持多线程:

libevent代码本身不支持多线程,因为源代码没有同步机制。

但是可以采用消息通知机制来支持多线程:

1.暴力抢占:当一个线程正在执行的时候,此时主线程来了一个任务此时立即抢占执行主线程的任务,此时好处是任务可以立即得到处理,但是你必须处理好切换的问题,过多的切换也会为CPU带来效率问题。

2.消息通知机制:当主进程有一个任务需要处理的时候会发送一个消息通知你去执行任务,此时当前进程还是执行自己的任务,在自己的任务执行完后,查看消息说通知有一个任务,再去处理任务,但是通知消息不是立即查看的,没有很好的实时性。

3.消息通知+同步层 :有个折中的处理方式,就是中间增减一个任务队列,这个任务队列是所有线程都可以看到的,每个线程都将新任务扔到这个队列中并且发送一个字符来通知,得到通知的当前线程只是取出其中的一个任务。当然,对于这个任务的 *** 作都是同步的,也就是每一个线程 *** 作要加锁,这就是一个加锁的队列。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/7286967.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-03
下一篇2023-04-03

发表评论

登录后才能评论

评论列表(0条)

    保存