文章目录
简单流程图
以上是使用libevent实现一个简单通信的基本流程图。
创建、注册、删除事件
使用libevent函数之前需要分配一个或多个event_base结构体。
每个event_base结构体持有一个事件集合,可以检测确定哪个事件是激活的。
如果需要多个线程检测I/O,则需要为每个线程都分配一个event_base结构体。
每个event_base都有一种用于检测哪种事件已经就绪的“方法”,或者说后端。
可以识别的方法有:
- select
- poll
- epoll等
用户禁止某些特定的后端的方法:
1、配置环境变量
比如说,要禁止kqueue后端,可以设置EVENT_NOKQUEUE环境变量。
2、用编程的方法禁止后端:
看下面关于event_config_avoid_method() 的说明
1、创建默认的event_base:event_base_new()
event_base_new()函数分配并且返回一个新的具有默认设置的event_base。
函数会检测环境变量,返回一个event_base的指针。如果发生错误,会返回NULL。
选择各种方法时,函数会选择OS支持最快的方法。
接口:
struct event_base *event_base_new(void)
2、释放event_base:event_base_free()
使用完event_base之后,要在程序的最后需要对event_base进行释放
接口:
void event_base_free(struct event_base *base)
注意:
这个函数不会释放当前与event_base关联的任何事件,或者关闭他们的套接字,或者释放任何指针
3、创建事件:event_new()
event_new()试图分配和构造一个用于base的新事件。
该函数出错时返回NULL
struct event *event_new(
struct event_base *base, //event_base_new的返回值
evutil_socket_t fd, //文件描述符 - int
short what, //宏:可监听的事件标志
event_callback_fn cb, //事件的回调处理函数
void *arg //回调函数传参
)
//事件回调处理函数
typedef void (*event_callback_fn)(evutil_socket_t, short, void *)
事件标志:
标志 | 含义 |
---|---|
EV_TIMEOUT | 这个标志表示某超时时间流逝后事件成为激活的。构造事件时通常忽略此标志;可以在添加事件时设置超时,也可以不设置。超时发生时,回调函数的what参数带有这个标志 |
EV_READ | 表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的 |
EV_WRITE | 表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的 |
EV_SIGNAL | 用于实现信号检测 |
EV_PERSIST | 表示事件是“持久的“ |
EV_ET | 如果底层的event_base后端支持边沿触发事件,则事件应该是边沿触发的。这个标志影响EV_READ和EV_WRITE的语义 |
what参数是上述标志的集合。
如果fd非负,则它是将被观察其读写事件的文件。事件被激活时,libevent将调用cb函数,传递这些参数:文件描述符fd、表示所有被触发事件的位字段、以及构造事件时的arg参数。
4、event_free()
释放事件
对未决或激活状态的事件调用event_free()是安全的;在释放事件之前,函数将会使事件成为非激活和非未决的。
void event_free(struct event *event)
添加事件
注意:创建事件后,在将其添加到event_base之前,实际上是不能对其做任何操作的,需要使用event_add()将事件添加到event_base里。
5、event_add()
所有新创建的事件都处于已初始化和非未决状态,调用event_add()可以使其成为未决的。
非未决状态:没有资格被处理
未决状态:有资格被处理但是还没有被处理
//成功返回0,失败返回-1
int event_add(
struct event *ev, //添加的事件
const struct timeval *tv //设置超时的时间
)
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
参数:
tv:
- NULL:添加的事件不会超时
- tv={0, n}:设置的时间;
以秒和微妙指定超时时间
添加事件循环
一旦有了一个已经注册某些事件的event_base,就需要让libevent等待事件并且通知事件的发生
6、event_base_loop()
#define EVLOOP_ONCE 0x01 //事件未被触发时,阻塞等待
#define EVLOOP_NONBLOCK 0x02 //非阻塞的方式来做事件检测,不关心事件是否被触发了
#define EVLOOP_NO_EXIT_NO_EMPTY 0x04 //没有事件的时候,不会退出轮询检测
//成功返回0,失败返回-1
int event_base_loop(
struct event_base *base,
int flags
)
默认情况下,该函数运行event_base直到其中已经没有注册的事件为止。
循环执行的时候,函数重复地检查是否有任何已经注册的事件被触发。
比如说,该事件的文件描述符已经就绪,就可以读取了;或者超时事件的超时时间即将到达。如果有事件被触发,函数标记被触发的事件为“激活的”,并且执行这些事件。
在flag参数中设置一个或多个标志就可以改变event_base_loop()的行为。
- 如果设置了EVLOOP_ONCE,循环将等待某些事件成为激活的,执行激活事件直到没有更多事件可以执行
- 如果设置了EVLOOP_NONBLOCK,循环不会等待事件被触发。循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调函数
7、event_base_dispatch()
等同于没有设置标志的event_base_loop(),所以循环将一直进行,直到已经没有注册的事件了;或者,调用了event_base_loopbreak()或event_base_loopexit()为止
int event_base_dispatch
(
struct event_base *base //event_base_new的返回值
)
退出事件循环函数
如果想在移除所有已注册的事件之前停止活动的事件循环,可以调两个稍有不同的函数。
这两个函数都:成功返回0,失败返回-1
8、event_base_loopexit()
该函数让event_base在给定时间后停止循环。
如果tv参数为NULL,event_base会立即停止循环,没有延时。
如果event_base当前正在执行任何激活事件的回调,则回调会继续运行,直到执行完所有激活事件的回调函数才退出
int event_base_loopexit(
struct event_base *base,
const struct timeval *tv
);
//参数:
struct timeval{
long tv_sec; //秒
long tv_usec; //微秒
};
9、event_base_loopbreak()
一旦调用,event_base立即退出循环
它与event_base_loopexit(base, NULL)的不同之处在于,如果event_base当前正在执行激活事件的回调,它将在执行完当前正在处理的事件后立即退出。
int event_base_loopbreak(struct event_base *base)
注意:event_base_loopexit(base, NULL)和event_base_loopbreak(base)在事件循环没有运行时的行为不同:
前者安排下一次事件循环在下一轮回调完成后立即停止(就好像自带EVLOOP_ONCE标志调用一样);后者却仅仅停止当前正在运行的循环,如果事件循环没有运行,则没有任何效果。