libevent & bufferevent

libevent & bufferevent,第1张

对于一个网络模块来说,一个缓冲模块是必不可少的,缓冲模块主要用于缓冲网络接收的模块,和用户发送的模块。

libevent在常规事件回调的基础上提供了一个缓冲的IO抽象概念,这个抽象概念被称为bufferevent、bufferevent提供了自动填充和释放的输入输出缓冲区,缓冲事件的用户不再直接处理IO,而是从输入缓冲区读取数据,写入到输出缓冲区。

libevent使用evbuffer作为网络缓冲模块,缓冲区由 evbuffer 和 evbuffer_chain 组成。evbuffer_chain是存储数据的一块内存,通过指针连接在一起,组成内存池,而evbuffer则是管理这个内存池的链表。

evbuffer_chain:属于evbuffer中单个项

evbuffer:

二者的关系

服务器用到libevent中bufferevent 中相关函数流程:

struct evconnlistener * evconnlistener_new_bind (struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,   const struct sockaddr *sa, int socklen)

申请一个evconnlistener 对象,在给定的ip地址端口上监听TCP连接。新的连接到来时会触发回调函数 cb;

内部实现是 完成socket(),bind,listen()这些函数之后,将此fd通过event_assign函数添加到event_base中,event_assign函数前篇有所介绍,这里不再详述。

之前说event与event_base进行关联使用的是event_add函数,这个evconnlistener_enable就是做这个这个事情,

libevent中实现了大量的函数指针,初始化时将evconnlistener_event_ops 地址绑定到ops,最后调用evconnlistener_enable 函数里面 *** 作lev->ops->enable(lev),就执行到了event_listener_enable函数中,次函数中再进行event_add *** 作。

可以理解为 evconnlistener_new_bind 函数封装了socket的API *** 作,然后对event与event_base进行初始化关联 *** 作,用户直接直接在回调中等待新的连接即可。

一个bufferevent包含了一个底层传输的fd,一个输入buffer一个输出buffer,并且bufferevent帮我们完成了从socket    上接收数据写入输入buffer,同时从输出buffer中取出数据通过sicket发送,当输入输出缓冲中的数据达到一定量的时候调用我们设置的回调函数。

bufferevent结构体中主要有

1 两个事件(读,写):基本的event,等待被触发后加入到base的活动队列,然后调用相应的回调函数,

2.两个缓冲区(input output):存储读取和待发送的数据

3三个回调函数(read write error):用户自定义

4两个超时时间:(读、写超时)

一: struct bufferevent * bufferevent_socket_new (struct event_base *base, evutil_socket_t fd, int options)

在给定的socket 上创建一个新的 socket bufferevent。

对bufferevent 结构体进行初始化 *** 作,包含以下几项:

1 读写缓冲区(input,output)的初始化

2.读写事件初始化 *** 作

二 :void bufferevent_setcb (struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb,    bufferevent_event_cb eventcb, void *cbarg)

 读写以及错误的回调函数的设置

三 :int bufferevent_enable (struct bufferevent *bufev, short event)

将事件添加到event_base上,

在 bufferevent_socket_new 中 对be_ops 进行了赋值 *** 作 ,&bufferevent_ops_socket,

bufferevent_init_common中 bufev->be_ops = ops即 bufev->be_ops =&bufferevent_ops_socket;

bufferevent_enable 中调用了be_ops

enable函数就是指be_socket_enable函数,

函数调用层层下来之后还是event_add函数,将event添加到event_base上,底层epoll的epoll_ctl *** 作,添加节点到红黑树上。

以上就是使用bufferevent对 socket 读写监听的流程。

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

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


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

原文地址:https://54852.com/bake/11560627.html

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

发表评论

登录后才能评论

评论列表(0条)

    保存