浅析libevent

浅析libevent,第1张

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

给你两个函数参考

omsTimer函数是处理定时事件,void(*handle)(union sigval v)参数就是处理事件的函数指针。

int omsSetTimer(timer_t *tId,int value,int interval)就是设置定时器。

按你说的,如果要同时起多个定时器,需要定义一个数组timer_t tm[n]int it[n]tm就是定时器结构,it用来记录对应的定时器是否已经使用,使用中的就是1,没用的就是0;

主进程消息来了就从it找一个没用的来omsSetTimer,如果收到终止消息,那omsSetTimer 定时时间为0

int omsTimer(timer_t *tId,int iValue,int iSeconds ,void(*handle)(union sigval v),void * param)

{

struct sigevent se

struct itimerspec ts

memset (&se, 0, sizeof (se))

se.sigev_notify = SIGEV_THREAD

se.sigev_notify_function = handle

se.sigev_value.sival_ptr = param

if (timer_create (CLOCK_REALTIME, &se, tId) <0)

{

return -1

}

ts.it_value.tv_sec = iValue

// ts.it_value.tv_sec =3

//ts.it_value.tv_nsec = (long)(iValue % 1000) * (1000000L)

ts.it_value.tv_nsec = 0

ts.it_interval.tv_sec = iSeconds

//ts.it_interval.tv_nsec = (long)(iSeconds % 1000) * (1000000L)

ts.it_interval.tv_nsec = 0

if (timer_settime(*tId, TIMER_ABSTIME, &ts, NULL) <0)

{

return -1

}

return 0

}

int omsSetTimer(timer_t *tId,int value,int interval)

{

struct itimerspec ts

ts.it_value.tv_sec =value

//ts.it_value.tv_nsec = (long)(value % 1000) * (1000000L)

ts.it_value.tv_nsec = 0

ts.it_interval.tv_sec = interval

//ts.it_interval.tv_nsec = (long)(interval % 1000) * (1000000L)

ts.it_interval.tv_nsec = 0

if (timer_settime(*tId, TIMER_ABSTIME, &ts, NULL) <0)

{

return -1

}

return 0

}


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

原文地址: https://outofmemory.cn/bake/11945919.html

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

发表评论

登录后才能评论

评论列表(0条)

保存