首先在学习libevent库的使用前,我们还要从基本的了解开始,已经熟悉了epoll以及reactor,然后从event_base学习,依次学习事件event、数据缓冲Bufferevent和数据封装evBuffer等,再结合具体的几个实例来了解libevent库的一些基本使用,有助于我们理解它的一些内部实现(由于之前我已经写过一篇epoll反应堆模型的,所以这里就不再介绍,直接从event_base开始介绍)。
libevent下载与安装:
在官网上找到 libevent-2.0.22-stable.tar.gz 下载地址。(版本可以自己选择,下面介绍的是Ubuntu下安装方法,其他系统参考)
tar -zxvf libevent-2.0.22-stable.tar.gz
cd libevent-2.0.22-stable/
./configure
make
sudo make install
libevent特点:
事件驱动,高性能;
轻量级,专注于网络;
跨平台,支持 Windows、Linux、Mac Os等;
支持多种 I/O多路复用技术, epoll、poll、dev/poll、select 和kqueue 等;
支持 I/O,定时器和信号等事件;
libevent组件:
evutil:用于抽象不同平台网络实现差异的通用功能。
event和event_base: libevent的核心,为各种平台特定的、基于事件的非阻塞 IO后端提供抽象API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测OS信号。
bufferevent: 为libevent基于事件的核心提供使用更方便的封装。除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时IO已经真正发生。( bufferevent接口有多个后端, 可以采用系统能够提供的更快的非阻塞 IO方式,如Windows中的IOCP。)
evbuffer:在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数
*****************************************************华丽的分隔线*************************************************************
event_base:
使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构 体持有一个事件集合,可以检测以确定哪个事件是激活的。如果设置 event_base 使用锁,则可以安全地在多个线程中访问它 。然而,其事件循环只能 运行在一个线程中。如果需要用多个线程检测 IO,则需要为每个线程使用一个 event_base。
event_base_new()函数分配并且返回一个新的具有默认设置的 event_base。函数会检测环境变量,返回一个到 event_base 的指针。如果发生错误,则返回 NULL。选择各种方法时,函数会选择 OS 支持的最快方法。
struct event_base *event_base_new(void);
使用完 event_base 之后,使用 event_base_free()进行释放。
void event_base_free(struct event_base *base);
注意:这个函数不会释放当前与 event_base 关联的任何事件,或者关闭他们的套接字 ,或 者释放任何指针
一旦有了一个已经注册了某些事件的 event_base, 就需要让 libevent 等待事件并且通知事件的发生。
#define EVLOOP_ONCE 0x01
#define EVLOOP_NONBLOCK 0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
int event_base_loop(struct event_base *base, int flags);
默认情况下,event_base_loop()函数运行 event_base 直到其中没有已经注册的事件为止。执行循环的时候 ,函数重复地检查是否有任何已经注册的事件被触发 (比如说,读事件 的文件描述符已经就绪,可以读取了;或者超时事件的超时时间即将到达)。如果有事件被触发,函数标记被触发的事件为 “激活的”,并且执行这些事件。
在 flags 参数中设置一个或者多个标志就可以改变 event_base_loop()的行为。如果设置了 EVLOOP_ONCE ,循环将等待某些事件成为激活的 ,执行激活的事件直到没有更多的事件可以执行,然会返回。如果设置了 EVLOOP_NONBLOCK,循环不会等待事件被触发: 循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调。
完成工作后,如果正常退出, event_base_loop()返回0;如果因为后端中的某些未处理 错误而退出,则返回 -1。
int event_base_dispatch(struct event_base *base);
event_base_dispatch ()等同于没有设置标志的 event_base_loop ( )。所以event_base_dispatch ()将一直运行,直到没有已经注册的事件了,或者调用 event_base_loopbreak()或者 event_base_loopexit()为止。
如果想在移除所有已注册的事件之前停止活动的事件循环,可以调用两个稍有不同的函数 。
int event_base_loopexit(struct event_base *base,const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
event_base_loopexit()让 event_base 在给定时间之后停止循环。如果 tv 参数为NULL, event_base 会立即停止循环,没有延时。
如果 event_base 当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出。
event_base_loopbreak ()让 event_base 立即退出循环。它与event_base_loopexit (base,NULL)的不同在于,如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出。
综上所述event_base处理过程主要如下:
1.调用event_base_new()创建一个event_base
2.注册了某些事件的 event_base
3.调用event_base_loop()或者event_base_dispatch()函数,循环等待事件并且通知事件的发生
4.调用event_base_loopexit()或者event_base_loopbreak()移除所有已注册的事件之前停止活动的事件循环
5.使用完 event_base 之后,使用event_base_free()进行释放
******************************************华丽的分割线*******************************************************
事件event:
libevent 的基本操作单元是事件。每个事件代表一组条件的集合,这些条件包括:
文件描述符已经就绪,可以读取或者写入
文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发 IO)
超时事件
发生某信号
用户触发事件
所有事件具有相似的生命周期。调用 libevent 函数设置事件并且关联到event_base 之后, 事件进入“已初始化(initialized)”状态。此时可以将事件添加到event_base 中,这使之进入“未决(pending)”状态。在未决状态下,如果触发事件的条件发生(比如说,文件描述 符的状态改变,或者超时时间到达 ),则事件进入“激活(active)”状态,(用户提供的)事件回调函数将被执行。如果配置为“持久的(persistent)”,事件将保持为未决状态。否则, 执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的 ; 添加操作可以让非未决事件再次成为未决的。
创建事件
使用 event_new()接口创建事件。
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10
#define EV_ET 0x20
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg);
void event_free(struct event *event);
event_new()试图分配和构造一个用于 base 的新的事件。
what 参数是上述标志的集合。
如果 fd 非负,则它是将被观察其读写事件的文件。
事件被激活时, libevent 将调用 cb 函数,
传递这些参数:文件描述符 fd,表示所有被触发事件的位字段 ,以及构造事件时的 arg参数。
发生内部错误,或者传入无效参数时, event_new()将返回 NULL。
所有新创建的事件都处于已初始化和非未决状态 ,调用 event_add()可以使其成为未决的。
释放事件
要释放事件,调用 event_free()。对未决或者激活状态的事件调用 event_free()是安全的:在释放事件之前,函数将会使事件成为非激活和非未决的。
事件标志
EV_TIMEOUT:这个标志表示某超时时间流逝后事件成为激活的。构造事件的时候,EV_TIMEOUT标志是 被忽略的:可以在添加事件的时候设置超时 ,也可以不设置。超时发生时,回调函数的 what 参数将带有这个标志。
EV_READ:表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的。
EV_WRITE:表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的。
EV_SIGNAL:用于实现信号检测,请看下面的 “构造信号事件”节。
EV_PERSIST:表示事件是“持久的”
EV_ET:表示如果底层的 event_base 后端支持边沿触发事件,则事件应该是边沿触发的。
事件的未决和非未决
设置未决事件:在非未决的事件上调用 event_add()将使其在配置的 event_base 中成为未决的。
int event_add(struct event *ev, const struct timeval *tv);
如果 tv 为 NULL,添加的事件不会超时。否则, tv 以秒和微秒指定超时值。
如果对已经未决的事件调用 event_add(),事件将保持未决状态,并在指定的超时时间被重新调度。
设置非未决事件:对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。
int event_del(struct event *ev);
如果在事件激活后,其回调被执行前删除事件,回调将不会执行。
一次触发事件
如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的 , 则可以使用 event_base_once()
int event_base_once(struct event_base *, evutil_socket_t, short,void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
除了不支持 EV_SIGNAL 或者 EV_PERSIST 之外,这个函数的接口与 event_new()相同。 安排的事件将以默认的优先级加入到 event_base并执行。回调被执行后,libevent内部将 会释放 event 结构。
事件总结:1.创建事件
2.事件的未决和非未决 设置未决事件调用event_add() 设置非未决事件调用event_del()
3.触发事件 一次触发事件调用event_base_once()
event代码示例
server.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <fcntl.h> 8 #include <event2/event.h> 9 10 //typedef void (*event_callback_fn)(evutil_socket_t, short, void *) 11 void callback_func(evutil_socket_t fd, short event, void *arg) 12 { 13 char buf[256] = {0}; 14 int len = 0; 15 16 printf("fd = %d, event = %d", fd, event); 17 18 len = read(fd, buf, sizeof(buf)); 19 20 if (len == -1) { 21 perror("read"); 22 return; 23 } else if (len == 0) { 24 perror("remote close fd"); 25 return; 26 } else { 27 buf[len] = '