一、libevent基本原理介绍
一个 event_base 对象相当于一个 Reactor 实例(不了解Reactor的读者可自行查询相关文章)。libevent默认情况下是单线程的,每个线程有且只有一个event_base,对应一个struct event_base结构体以及附于其上的事件管理器,用来调度托管给它的一系列event。
当一个事件发生后,event_base会在合适的时间,去调用绑定在这个事件上的函数,直到这个函数执行完,再去调度其他的事件。
event_base内部有一个循环,循环阻塞在epoll等系统调用上,直到有一个/一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上,每个事件对应一个struct event,可以是监听一个fd或者信号量之类的,struct event使用event_new来创建和绑定,使用event_add来将event绑定到event_base中。
相关的代码我们前面已经写过了,这里再贴一下:
//创建一个event_base
struct event_base *base = event_base_new();
// 创建并绑定一个event
struct event* listen_event;
//参数:event_base,监听的对象,需要监听的事件,事件发生后的回调函数,传给回调函数的参数
listen_event = event_new(base, listener, EV_READ | EV_PERSIST, callback_func, (void*)base);
//参数:event,超时时间,NULL表示无超时设置
event_add(listen_event, NULL);
libevent支持的事件及属性:
(1)EV_TIMEOUT:超时;
(2)EV_READ:只要网络缓冲中还有数据,回调函数就会被触发;
(3)EV_WRITE:只要塞给网络缓冲的数据被写完,回调函数就会被触发;
(4)EV_SIGNAL:POSIX信号量;
(5)EV_PERSIST:不指定这个属性,回调函数被触发后事件会被删除;
(6)EV_ET:Edge-Trigger边缘触发
事件发生时的回调函数callback_func的原型如下所示:
typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)
所以总结一下,对于一个服务器而言,流程大致如下:
(1)获取待监听的内容的fd;
(2)创建一个event_base;
(3)创建一个event,指定待监听的fd,待监听事件的类型,以及事件发生时的回调函数及传给回调函数的参数;
(4)将event添加到event_base的事件管理器中;
(5)开启event_base的事件处理循环;
(6)(异步)当事件发生的时候,调用前面设置的回调函数。
二、开始循环 - event_base_dispatch函数
event_base_dispatch是通过调用event_base_loop函数实现的。
int
event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, 0));
}
int
event_base_loop(struct event_base *base, int flags){...}
event_base_loop的flags属性有以下几个选项:
(1)#define EVLOOP_ONCE 0x01
等待一个事件运行,事件只会被触发一次。
(2)#define EVLOOP_NONBLOCK 0x02
有活动事件就处理,没有就返回0。在这种情况下,程序一启动就会马上终止。下面2种情况下是等价的:
//1.
bool isexit = false;
while(!isexit){
event_base_loop(base, EVLOOP_NONBLOCK);
}
//2.
event_base_dispatch(base);
(3)#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
没有事件的时候, 也不退出轮询检测,用于事件后期多线程添加。
//当没有event_add()事件时
//1. 程序启动后会直接返回
event_base_dispatch(base);
//2. 程序启动后阻塞等待
event_base_loop(base, EVLOOP_NO_EXIT_ON_EMPTY);
所以event_base_dispatch()等同于没有设置标志的 event_base_loop()
将一直运行,直到没有已经注册的事件了,或者调用 了event_base_loopbreak()或者 event_base_loopexit()为止。
三、停止循环
struct timeval{
long tv_sec;
long tv_usec;
};
(1)event_base_loopexit()
如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出。
int event_base_loopexit(
struct event_base *base,
const struct timeval *tv
);
(2)event_base_loopbreak()
让event_base 立即退出循环。
int event_base_loopbreak(struct event_base *base);
四、测试例子
test_signal_loop.cpp:
#include <iostream>
#include <string.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/listener.h>
using namespace std;
//socket 文件描述符
//which 事件类型
//arg 传递的参数
static void Ctrl_C(int socket, short which, void* arg){
cout<<"Ctrl_C"<<endl;
event_base* base = (event_base*)arg;
//执行完当前处理的事件函数就退出
//event_base_loopbreak(base);
//运行所有的活动事件再退出
//事件循环没有运行时也要等运行一次再退出
timeval t = {3, 0};//至少运行3秒后退出
event_base_loopexit(base, &t);
}
static void Kill(int socket, short which, void* arg){
cout<<"Kill"<<endl;
//加上下面这段代码后,event又变被重新pending
event* ev = (event*)arg;
if(!evsignal_pending(ev, NULL)){
event_del(ev);
event_add(ev, NULL);
}
}
int main()
{
//创建libevent上下文
event_base* base = event_base_new();
if (base) {
cout<<"event_base_new success!"<<endl;
}
//添加ctrl +c信号事件,处于no_pending状态
//evsignal_new隐藏的状态: EV_SIGNAL|EV_PERSIST
event *csig = evsignal_new(base, SIGINT, Ctrl_C, base);
if(!csig){
cerr<<"evsignal_new csig failed!"<<endl;
return -1;
}
//添加事件到pending
if(event_add(csig, 0) != 0){
cerr <<"event_add csig failed!"<< endl;
return -1;
}
//添加kill信号
//EV_SIGNAL: 没有添加EV_PERSIST,表面当前event为非持久化事件,只能执行1次
//event_self_cbarg(): 传递当前的event
event *ksig = event_new(base, SIGTERM, EV_SIGNAL, Kill, event_self_cbarg());
if(!ksig){
cerr<<"event_new ksig failed!"<<endl;
return -1;
}
//添加事件到pending
if(event_add(ksig, 0) != 0){
cerr <<"event_add ksig failed!"<< endl;
return -1;
}
//事件分发处理
if (base) {
//event_base_dispatch(base);
//1. EVLOOP_ONCE: 等待一个事件运行,事件只会被触发一次.
//2. EVLOOP_NONBLOCK: 有活动事件就处理,没有就返回0.
//3. EVLOOP_NO_EXIT_ON_EMPTY: 没有事件的时候, 也不退出轮询检测.
event_base_loop(base, EVLOOP_NO_EXIT_ON_EMPTY);
}
if (csig) {
event_free(csig);
}
if (base) {
event_base_free(base);
}
return 0;
}