回调函数参数说明
libevent的回调函数的格式就是三个参数
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
前两个参数不用我们传(目前我学的浅,只传递过第三个参数),第三个参数是一个通用的指针,可以选择传也可以选择不传
事件状态
EV_READ 可读
EV_WRITE 可写
EV_SIGNAL 信号事件
EV_PERSIST 永久性(很多事件默认触发只触发一次,加上这个就可以永久性的触发了)
EV_ET 边沿触发(用于epoll模型)
事件创建函数
struct event *event_new(struct event_base *base,
evutil_socket_t fd, short what, event_callback_fn cb, void *arg);
参数 what 意思就是上面所讲的事件状态,cb是回调函数,arg想给回调函数传递的参数,如果想传刚创建的event本身,可使用event_self_cbarg()函数
event_callback_fn函数指针的类型
typedef void (*event_callback_fn)(evutil_socket_t sock fd,
short what, void *arg);
通用的事件创立的函数,信号事件的话也有再次进行了封装的函数,下面信号事件代码也是展示了两种不同事件创立信号事件。
信号事件对event_new再次封装,默认加了这两种状态 EV_SIGNAL|EV_PERSIST(信号事件和可持久事件)。
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
事件创建完成之后,要把事件添加进去
int event_add(struct event *ev, const struct timeval *timeout);参数1、事件对象,参数2、定时参数,返回值0 是成功
创建事件->添加事件->放主循环当中去(event_base_dispatch(base))
事件的详解
1.信号事件
这里主要讲解两个信号,SIGINT(ctrl + c)和SIGTERM(kill、pkill)
如果采用evsignal_new函数来创建一个信号事件,那么该信号事件就是永久性的了,可以看上面evsignal_new宏函数定义。如果使用通用的事件函数创建,想要永久激活,有两种做法,一是 EV_SIGNAL | EV_PERSIST另一种做法是在回调函数再次把事件重新打开
if(!evsignal_pending(ev,NULL)){
event_del(ev);
//重新添加
event_add(ev,NULL);
}
完整代码如下:
#include <iostream>
#include <event2/listener.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/socket.h>
using namespace std;
//第二个which是属性
static void Ctrl_C(int sock,short which,void *arg){
cout<<"Ctrl_C"<<endl;
}
static void kill(int sock,short which,void *arg){
cout<<"kill"<<endl;
event* ev = (event*)arg;
//如果处于非待决状态
if(!evsignal_pending(ev,NULL)){
event_del(ev);
//重新添加
event_add(ev,NULL);
}
}
int main(int argc,char** argv){
int port = 9996;
if(argc > 1){
port = atoi(argv[1]);
}
//忽略管道信号
if(signal(SIGPIPE,SIG_IGN) == SIG_ERR){
return 1;
}
//创建event的上下文
event_base* base = event_base_new();
if(base){
cout<<"event_base_new successful!"<<endl;
}
//添加一个信号ctrl+c,处于no pending状态
event* event_sig = evsignal_new(base,SIGINT,Ctrl_C,base);
if(!event_sig){//失败的话
cerr<<"event_signal error"<<endl;
return -1;
}
//signal添加到事件,第二个参数是超时时间
if(event_add(event_sig,0)!=0){//==0是成功
cerr<<"add failed"<<endl;
return -1;
}
//添加kill信号,非持久只进入一次,event_self_cbarg()传递当前的event
event* ksig = event_new(base,SIGTERM,EV_SIGNAL,
kill,event_self_cbarg());
if(!ksig){//失败的话
cerr<<"ksignal failed"<<endl;
return -1;
}
//signal添加到事件,第二个参数是超时时间
if(event_add(ksig,0)!=0){//==0是成功
cerr<<"ksig failed"<<endl;
return -1;
}
//事件分发处理,事件主循环
if(base)
event_base_dispatch(base);
if(base)
event_base_free(base);
return 0;
}
2.定时事件
也是在通用的事件创建函数进行了封装创建函数。 evtimer_new和event_new两种创建。添加事件event_add也是两种,那种都可以,已经测试了。定时事件默认使用二叉堆插入删除都是O(logN),如果超时时间相同的话可以使用如下操作。改为使用双向队列,插入删除O(1)
static timeval tv_in={3,0};
event* evtime3 = event_new(base,-1,EV_PERSIST,timer3,0);
const timeval *t3 = event_base_init_common_timeout(
base,&tv_in);
event_add(evtime3,t3);
#include <iostream>
#include <event2/listener.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/socket.h>
using namespace std;
static timeval t1= {1,0};
void timer1(int,short,void *arg){
auto ev = (event*)arg;
//只会触发一次,可以重新添加
if(!evtimer_pending(ev,&t1)){
event_del(ev);
event_add(ev,&t1);
}
cout<<"timer1"<<endl<<flush;
}
void timer2(int,short,void*){
cout<<"timer2"<<endl;
}
int main(int argc,char** argv){
int port = 9996;
if(argc > 1){
port = atoi(argv[1]);
}
//忽略管道信号
if(signal(SIGPIPE,SIG_IGN) == SIG_ERR){
return 1;
}
//创建event的上下文
event_base* base = event_base_new();
if(base){
cout<<"event_base_new successful!"<<endl;
}
//定时器处理
event* evtime = evtimer_new(base,timer1
,event_self_cbarg());
if(!evtime){
cout<<"evtimer failed "<<endl;
return -1;
}
event_add(evtime,&t1);
event* evtime2 = event_new(base,-1,EV_PERSIST,timer2,0);
evtimer_add(evtime2,&t1);
if(base)
event_base_dispatch(base);
if(base)
event_base_free(base);
if(evtime)
event_free(evtime);
if(evtime2)
event_free(evtime2);
return 0;
}
监控文件事件
还是一样的事件,只不过课程视频监测的是root登录日志,而我再普通用户下没有办法监测登录日志,天真的我以为随便监听个事件然后手打内容就ok了,可惜还是太年轻了。那样并不能真的监听到文件,我又猜想我的代码是否有问题?拿到root权限下试一下监测登录日志,发现ok可以监测到。我又开始猜想是不是要系统把内容拷贝到文件而不是手打进去的?有了这个想法我又开始写了一个read函数,发现OK,可以监测到了。
监测文件代码如下:
#include <iostream>
#include <event2/listener.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <thread>
using namespace std;
void read_file(int fd,short,void* ){
char buf[1024] = { 0 };
int len = read(fd,buf,sizeof(buf));
if(len > 0){
cout<<buf<<endl;
}
else{
// cout<<"."<<endl;
this_thread::sleep_for(2500ms);
}
}
int main(int argc,char** argv){
int port = 9996;
if(argc > 1){
port = atoi(argv[1]);
}
//忽略管道信号
if(signal(SIGPIPE,SIG_IGN) == SIG_ERR){
return 1;
}
event_config* conf = event_config_new();
//设置支持文件描述符,这样才可以监听文件
event_config_require_features(conf,
EV_FEATURE_FDS);
//根据特征来生成base
event_base* base = event_base_new_with_config(conf);
if(!base){
cerr<<"base failed"<<endl;
return -1;
}
int fd = open("listened.log",O_RDONLY | O_NONBLOCK);
if(fd <= 0){
cerr<<"open failed"<<endl;
return -1;
}
//文件指针移到结尾处
// lseek(fd,0,SEEK_END);
event* fv = event_new(base,fd,EV_READ | EV_PERSIST
,read_file,0);
event_add(fv,NULL);
//要进入主循环
if(base)
event_base_dispatch(base);
if(conf){
event_config_free(conf);
}
if(base){
cout<<"event_base_new successful!"<<endl;
}
if(base)
event_base_free(base);
return 0;
}
read函数文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <thread>
#include <iostream>
#include <string.h>
using namespace std;
int main(){
int fd = open("listened.log",O_WRONLY,0);
if(fd <= 0){
cout<<"open failed"<<endl;
return -1;
}
lseek(fd,0,SEEK_END);
const char* buff = "dxgzg";
for(int i = 0;i < 5;++i){
int flag = write(fd,buff,strlen(buff));
this_thread::sleep_for(2000ms);
}
return 0;
}
终于可以监测到文件了