事件根基event_base、事件循环event_loop、事件event、信号事件SignalEvent

一.event_base

(一) libevent简介与浅谈event_base

事件根基event_base、事件循环event_loop、事件event、信号事件SignalEvent_第1张图片

  1. libevent实际上就是对底层select/poll/epoll等进行了封装,每个event_base都有一种“方法”,该“方法”是select、poll、epoll、kqueue、devpoll、evport、win32。
  2. 使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个 event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。
  3. event_base 相当于是一个底座,只要向底座上插入事件,然后不断的监控事件,等待事件发生调用回调函数即可

(二) event_base的API

1. 检查event_base后端方法(IO多路复用方法)

函数声明 功能
const char **event_get_supported_methods(void); 返回一个指针 ,指向 libevent 支持的IO多路方法名字数组,这个数组的最后一个元素是NULL
const char *event_base_get_method(const struct event_base *base); 返回 event_base 正在使用的IO多路方法
enum event_method_feature event_base_get_features(const struct event_base *base); 返回 event_base 支持的特征的比特掩码
//libevent的版本
printf("Starting Libevent %s. Available methods are:\n", event_get_version());

//检查支持的IO多路方法
const char **methods = event_get_supported_methods();
for (int i=0; methods[i] != NULL; ++i) {
	printf(" %s\n", methods[i]);
}
struct event_base *base = event_base_new();
enum event_method_feature f;

if (!base) 
{
	puts("Couldn't get an event_base!");
} 
else 
{
	//返回 event_base 正在使用的IO多路方法
	printf("Using Libevent with backend method %s\n",event_base_get_method(base));
	
	//返回 event_base 支持的特征的比特掩码
	f = event_base_get_features(base);
	if ((f & EV_FEATURE_ET)) //支持边沿触发的后端
		printf(" Edge-triggered events are supported.");
	if ((f & EV_FEATURE_O1)) //添加、删除单个事件,或者确定哪个事件激活的操作是 O(1)复杂度的后端
		printf(" O(1) event notification is supported.");
	if ((f & EV_FEATURE_FDS)) //要求支持任意文件描述符,而不仅仅是套接字的后端
		printf(" All FD types are supported.");
}

2. 创建事件根基event_base_new

struct event_base *event_base_new(void);
功能:函数会检测环境变量,返回一个event_base的指针,分配并且返回一个新的具有默认设置的 event_base。

3. 释放事件根基event_base_free

void event_base_free(struct event_base *base); //释放event_base
注意:这个函数不会释放当前与 event_base 关联的任何事件,或者关闭它们的套接字 ,或者释放任何指针。应该手动的释放它们

4. event_base和fork

int event_reinit(struct event_base *base);
因为不是所有的安插在event_base的事件在调用fork()之后都可以正常工作,所以,如果在使用fork()或者其他相关系统调用启动一个新的进程之后,要想在子进程中使用base变量,但是又想让该base变量是一个全新的没有安插事件的变量,就应该在子进程中对base调用event_reinit函数进行重新初始化。

[伪代码]
	struct event_base* base=event_base_new();
	
	//向event_base中安插事件
	
	if(fork()) // parent process
	{
		continue_runing_parent(base); 
	}
	else //child process
	{
		event_reinit(base); //重新初始化子进程从父进程继承下来的base
		continue_runing_child(base);
	}

5. 设置event_base支持的优先级别个数

int event_base_priority_init(struct event_base *base, int n_priorities);

  • 功能:给event_base设置共有n_priorities个优先级级别,以便于事件安插在event_base之前可以设置[0, n_priorities-1]级别的优先级
  • 返回值:成功时这个函数返回 0,失败时返回 -1。
  • 参数:
    • base是要修event_base
    • n_priorities是要支持的优先级数目,这个数目至少是 1 。每个新的事件可用的优先级将从 0 (最高) 到 n_priorities-1(最低)。常量 EVENT_MAX_PRIORITIES 表示 n_priorities 的上限。

默认情况下,与 event_base 相关联的事件的优先级将默认被初始化为 n_priorities / 2


二.事件循环 event_loop

一旦创建好事件根基event_base,并且在根基上安插好事件之后,需要对事件循环监控(换句话说就是等待事件的到来,触发事件的回调函数),有两种方式可以达到上面描述的功能,即:event_base_dispatch和event_base_loop

(一) 事件循环

方式1:int event_base_dispatch(struct event_base *);

方式2:int event_base_loop(struct event_base *base, int flags);

  • 参数flags
    • EVLOOP_ONCE:相当于epoll_wait阻塞方式&&只调用一次 ⇒ 当没有事件到来时,程序将一直阻塞在event_base_loop函数;直到有任意一个事件到来时,程序才不会阻塞在event_base_loop,将会继续向下执行。
    • EVLOOP_NONBLOCK:相当于epoll_wait非阻塞方式&&只调用一次 ⇒ 即使没有事件到来,程序也不会阻塞在event_base_loop
    • EVLOOP_NO_EXIT_ON_EMPTY:等价于event_base_dispatch ⇒ 将一直循环监控事件 ⇒ 直到没有已经注册的事件 || 调用了event_base_loopbreak()或 event_base_loopexit()为止

(二) 终止事件循环

event_base_dispatch函数退出的三种情况
当所有的事件都active完毕,此时没有处于未决状态(正在被监听)的事件时
调用event_base_loopexit(struct event_base*, const struct timeval*)函数
调用event_base_loopbreak(struct event_base*)函数

如果想在移除所有已注册的事件之前停止活动的事件循环,可以调用两个稍有不同的函数

函数声明 功能与区别
int event_base_loopexit(struct event_base *base, const struct timeval *tv); 让 event_base 在给定时间之后停止循环。如果 tv 参数为 NULL ,event_base 会立即停止循环,没有延时。如果 event_base 当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出
int event_base_loopbreak(struct event_base *base); 让 event_base 立即退出循环(即使有其他正在执行任何激活事件的回调)

(3)转储event_base的状态

void event_base_dump_events(struct event_base *base, FILE *f);
功能:将event_base上安插的事件情况,写入到绑定的文件中

FILE *fp = fopen("event_base_stat.txt","a"); 
event_base_dump_events(base, fp); //将base的状态,保存到文件中
fclose(fp);

三.事件event

(一) 事件状态转换图

事件根基event_base、事件循环event_loop、事件event、信号事件SignalEvent_第2张图片
event的使用步骤

在base上安装、监控event的流程
struct event* ev=event_new(base,fd,what,cb,arg)创建事件,事件处于非未决/初始化状态
event_add(ev,NULL); //使事件处于未决状态
event_base_dispatch(base); //循环检测事件,事件发生时触发回调函数
event_free(ev);释放事件
event_base_free(base);释放event_base

信号事件signalEvent、事件event的使用步骤对比
可以看到:

  1. signalEvent和event的类型一样,都是struct event*
  2. 使用过程也完全一样
  3. 唯一的不同点,是在创建struct event*对象时不同,分别为evsignal_new/event_new
  4. 警告:不要在信号事件上设置超时,这可能是不被支持的。[待修正:真是这样的吗?]
信号事件signalEvent 事件event
struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); struct event* ev=event_new(base,fd,what,cb,arg)
event_add(signal_event, NULL) event_add(ev,NULL);

(二) 事件event相关API

1.创建事件: event_new \ event_assign

创建并初始化struct event类型的事件变量,根据创建的位置分为堆/栈

创建位置 函数原型
堆区 struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, void (*cb)(evutil_socket_t, short, void *), void *arg);
栈区 int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
event_new event_assign
功能 试图在堆上分配和构造一个用于 base 的新的event 在栈上已经初始化好struct event类型的变量,使用event_assign函数对其进行初始化
使用步骤 struct event * ev; struct event ev;
ev=event_new(_,_,_,_,__); event_assign(&ev,_,_,_,_,__);
警告 不要对已经在 event_base 中处于未决状态的事件调用 event_assign(如果已经初始化和成为未决的,需要调用event_assign之前需要调用event_del)

参数介绍

  • fd:如果fd非负,表示被观察其读写事件的文件
  • cb/arg:事件激活时,调用的回调函数cb和参数arg
  • what:
    • EV_TIMEOUT 超时
    • EV_READ 可读
    • EV_WRITE 可写
    • EV_SIGNAL 用于实现信号检测,请看下面的 “构造信号事件”节
    • EV_PERSIST 事件是“持久的”
    • EV_ET 边沿触发事件

[重点]关于事件持久性:EV_PERSIST

(1)是否设置EV_PERSIST标志的本质区别:事件从active状态结束后,将变成 [ 未决状态 / 非未决状态 ] ?

是否设置EV_PERSIST持久标志? 事件在进入active状态并且回调函数执行完毕后,事件状态从active状态变成什么状态?
设置 未决状态(事件仍然被监听)
未设置 非未决状态(事件已经不被监听)

(2)分析程序,深刻的体会设置/不设置EV_PERSIST的区别

输入任意字符,按回车后,代码的执行结果
情况1:不设置EV_PERSIST持久标志 事件被触发后,事件状态进入非未决状态,此时没有被监听的事件 ==> 导致程序执行callback后,直接退出
情况2:设置EV_PERSIST持久标志 事件被触发后,事件状态进入未决状态,此时事件仍然被监听;并且缓冲区的数据一直存在 ==> 导致程序一直执行callback函数,不断的打印横线
void event_cb(evutil_socket_t fd, short what, void *arg)
{
  printf("____________________\n");
}

int main()
{
  struct event_base* base = event_base_new();
  
  int fd = 0;
  //情况1:不设置EV_PERSIST持久标志
  struct event* ev = event_new(base,fd,EV_TIMEOUT|EV_READ/*|EV_PERSIST*/,event_cb,NULL);
  //情况2:设置EV_PERSIST持久标志
  struct event* ev = event_new(base,fd,EV_TIMEOUT|EV_READ|EV_PERSIST,event_cb,NULL);

  event_add(ev,NULL); //使事件处于未决状态

  event_base_dispatch(base); //循环检测事件,直到没有要注册的事件或者调用exit/break函数

  event_free(ev); // 释放事件 void event_free(struct event *event);
  event_base_free(base);
  return 0;
}

2.event_add \ event_del

功能:让事件从非未决状态/未决状态之间切换
int event_add(struct event *ev, const struct timeval *tv);
int event_del(struct event *ev);

3. 带优先级的事件:event_priority_set

int event_priority_set(struct event *event, int priority); //设置事件event的优先级priority,其中priority属于[0, n_priorities-1]

设置事件优先级的步骤 相关API使用
1. 创建event_base和event event_base_new()、event_new()
2. 设置event_base支持的[优先级的数目n_priorities] event_base_priority_init(base, n_priorities);
3. 设置event的优先级 event_priority_set(ev, 0);
4. 将event安插到event_base上

说明:如果不为事件event设置优先级,则默认的优先级等于 event_base 的优先级数目n_priorities除以2

4. 手动激活事件: event_active

void event_active(struct event *ev, int what, short ncalls);
功能:使没被触发的事件,成为active状态
重点

  1. 事件不需要已经处于未决状态:即,不调用event_add(ev)的事件ev,也能被event_active触发
  2. 激活事件也不会让它成为未决的激活事件也不会让它成为未决的:即,事件active后,还是保持未active之前的状态,并不是进入未决状态

代码案例
程序执行结果:程序执行一次event_cb函数后,直接退出
分析原因:调用event_active函数后,事件ev并没有变成“未决状态”,导致此时没有待监控的事件,所以event_base_dispatch函数返回,程序退出

void event_cb(evutil_socket_t fd, short what, void *arg)
{
  printf("____________________\n");
}

int main()
{
  struct event_base* base = event_base_new();
  
  int fd = 0;
  
  //EV_PERSIST永久监控EV_READ事件
  struct event* ev = event_new(base,fd,EV_TIMEOUT|EV_READ|EV_PERSIST,event_cb,NULL); 

  //event_add(ev,NULL);  //不需要通过event_add使事件ev处于未决状态

  event_active(ev,EV_READ,0); //触发事件后,事件并没进入未决状态

  event_base_dispatch(base);
  event_free(ev);
  event_base_free(base);
  return 0;
}

5. 一次触发事件event_base_once

函数原型:int event_base_once(struct event_base *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);

  1. event_base_once()函数除了不支持EV_SIGNAL或EV_PERSIST外,它与event_new()函数相同
  2. 事件回调函数执行后,libevent内部会将会释放event结构
  3. event_base_once ()插入的事件不能删除/取消 或 手动激活

你可能感兴趣的:(libevent读书笔记)