libevent
可以处理I/O事件,信号事件(转化为I/O事件进行处理),以及定时事件。
I/O框架库:封装select,poll,epoll,提供了一个更加便捷的接口。(更高效,更稳定。)
- 基于Reactor模式实现:
(共有5各组件)
1.句柄 (学习,学语文,学数学,学英语,对应处理语文课本,数学课本,英语课本)
I/O框架要处理的对象有: (I/O事件,信号事件,定时时间),统称:事件源
I/O事件对应的句柄是文件描述符,信号事件对应的句柄是信号值。
2.事件多路分发器(事件的到来时随机,异步的,无法程序何时收到一个客户的连接请求,获取这暂停信号),最好的办法就是,程序一直循环等待处理事件,每隔一段时间就访问一下,简单说就是通过统一接口调用select,poll,epoll_wait
来等待事件。(demultiplex方法就是这些函数的调用接口)
还需要:register_event,remove_event方法往事件多路分发器中,添加事件和删除事件。
3.事件处理器和具体事件处理器
事件处理器的功能:即执行回调(一个或多个)函数(handle_event)在事件循环中,I/O框架提供的事件处理器通常是接口,需要用户自己继承它,来实现自己的事件处理器,也就是具体的事件处理器。因此定义为虚函数,方便扩展。
(还提供了get_handle),返回和事件处理器相关的句柄。
(当事件多路分发器检测到有事件产生,会通过句柄来通知应用程序,接下来事件处理器才会去循环处理这些事件),因此句柄和事件处理器是绑定在一块的。
举个例子:比如I/O事件对应句柄是文件描述,而通过回调函数获取的文件描述符是就绪文件描述符,
举个例子,比如I/O函数检测到内核中有就绪事件,回调函数就去处理这些就绪时间,比如回调函数中封装了recv,就是通过recv去接收数据。
5.Reator(整个I/O框架库的核心)
提供的主要方法:
1.handle_event:执行事件的循环,重复:等待事件,依次处理就绪事件对应的事件处理器。
2.register_handler: 该方法通过调用时间多路分发器中的register_event方法来往时间多路分发器中添加事件
3.remove——hander:该方法通过调用时间多路分发器中的remove_event方法来往时间多路分发器中删除事件。
整个I/O框架处理事件如下图:
执行:stu@stu-virtual-machine:~/linux/day22$ gcc -o main main.c -levent
ctrl + \ 结束程序
创建具体事件处理器(利用回调函数)在循环事件中执行
- 信号事件
//struct event* sig_ev = evsignal_new(base,SIGINT,sig_cb,NULL);实际就是调用的是event_new
struct event *sig_ev = event_new(base,SIGINT,EV_SIGNAL|EV_PERSIST,sig_cb, NULL);
如果没有EV_PERSIT
**sig只介入一次。**信号只触发一次
定时事件
//struct event*time_ev = evtimer_new(base,time_cb,NULL);实际就是调用的是event_new
struct event *time_ev = event_new(base,-1,EV_TIMEOUT|EV_PERSIST,time_cb,N ULL);
有EV_PERSIST
,可以一直处理定时事件
主动退出事件:
event_base_loopexit(base,&delay);
- 初始化一个libevent库,并保存返回指针
//定义实例框架,创建event_base对象,就可以使用Reactor中的方法
struct event_base *base = event_init();
assert(base != NULL)
- 首先对应用程序进行初始化event,设置好事件类型和回调函数
如信号事件,定时事件:
//struct event* sig_ev = evsignal_new(base,SIGINT,sig_cb,NULL);
struct event *sig_ev = event_new(base,SIGINT,EV_SIGNAL,sig_cb,NULL);//EV_PERSIST作用:事件触发后,自动重新对这个event调用event_add函数
也可以是:
void event_set(struct event *ev, int fd, short event, void (*cb)(int,
short, void *), void *arg)
ev
:执行要初始化的 event 对象;
fd
:该 event 绑定的“句柄”,对于信号事件,它就是关注的信号;
event
:在该 fd 上关注的事件类型,它可以是 EV_READ
, EV_WRITE
, EV_SIGNAL
;
cb
:这是一个函数指针,当 fd 上的事件 event 发生时,调用该函数执行处理,它有三个参数,
调用时由 event_base 负责传入,按顺序,实际上就是 event_set 时的 fd, event 和 arg;
arg
:传递给 cb 函数指针的参数;
void sig_cb(int fd,short ev,void *arg)
{
printf("sig =%d\n",fd);
}
void time_cb(int fd,short ev,void *arg)
{
printf("time out\n");
}
int main()
{
//定义实例框架,创建event_base对象,就可以使用Reactor中的方法
struct event_base *base = event_init();
assert(base != NULL);
//IO,sig,timeout
// struct event* sig_ev = evsignal_new(base,SIGINT,sig_cb,NULL);
// 定时器传入的是-1
struct event *sig_ev = event_new(base,SIGINT,EV_SIGNAL,sig_cb,NULL);//EV_PERSIST作用:事件触发后,自动重新对这个event调用event_add函数
assert(sig_ev!=NULL);
event_add(sig_ev,NULL);//null:定时时间
struct timeval tv = {2,0};
//struct event*time_ev = evtimer_new(base,time_cb,NULL);
struct event *time_ev = event_new(base,-1,EV_TIMEOUT|EV_PERSIST,time_cb,NULL);
//吃触发一次就停下来,还是一直处罚,由需求决定。
event_add(time_ev,&tv); //注册事件
event_base_dispatch(base); //事件循环->select poll epoll 阻塞
event_free(time_ev);
event_free(sig_ev);
event_base_free(base);
exit(0);
}
回调函数还是
- 没有就绪描述符的时候,主动关闭描述符
struct event** p_ev = (struct event**)malloc(sizeof(struct event*));
//,将当前文件描述符进行设置,传给p_ev指针
//通用事件处理器(设置事件类型,回调函数)
*p_ev = event_new(base,c,EV_READ|EV_PERSIST,recv_cb,p_ev);
if(*p_ev == NULL)
{
return ;
}
//将事件添加到libevent中
event_add(*p_ev,NULL);
//注销事件 ,关闭描述符
event_free(*p);
free(p);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<event.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
//回调函数
int socket_init();
void recv_cb(int fd,short ev,void *arg)
{
struct event **p = (struct event**)arg;//将p_ev强转过来
if(p == NULL)
{
return ;
}
if(ev & EV_READ)
{
char buff[128] = {0};
int n = recv(fd,buff,127,0);
if(n <= 0)
{
//注销事件 ,关闭描述符
event_free(*p);
free(p);
close(fd);
printf("client over\n");
}
else
{
printf("buff(%d) == %s\n",fd,buff);
send(fd,"ok",2,0);
}
}
}
//回调函数
void accept_cb(int fd,short ev,void *arg)
{
struct event_base *base = (struct event_base*)arg;
//将base强转为base;
//如果是读事件
if(ev & EV_READ)
{
//对就绪事件进行处理。
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(fd,(struct sockaddr*)&caddr,&len);
//得到一个新的文件描述符
if(c<0)
{
return ;
}
//如何将c存起来‘
printf("accept c =%d\n",c);
struct event** p_ev = (struct event**)malloc(sizeof(struct event*));
//,将当前文件描述符进行设置,传给p_ev指针
//通用事件处理器(设置事件类型,回调函数)
*p_ev = event_new(base,c,EV_READ|EV_PERSIST,recv_cb,p_ev);
if(*p_ev == NULL)
{
return ;
}
//将事件添加到libevent中
event_add(*p_ev,NULL);
}
// if(ev&EV_TIMEOUT)
}
int main()
{
int sockfd = socket_init();
assert(sockfd != -1);
//创建一个libevent库
struct event_base *base = event_init(); //不能在多线程中使用,因为会使用到同一个全局变量,释放时造成内存泄漏 event_base_new 取消全局变量
assert(base != NULL);
//设置好事件类型,设置回调函数(监听套接子)
struct event *sock_ev = event_new(base,sockfd,EV_READ|EV_PERSIST,accept_cb,base); // 传入base,因为添加连接套接子还要用到。
assert(sock_ev != NULL);
//将事件添加到libevent库中
event_add(sock_ev,NULL);
//程序进入无限循环,等待就绪事件被select/poll/epoll_wait处理。(如果有就绪事件,(读写信号)启用回调函数)
//ecent_base_loop循环遍历
event_base_dispatch(base);
event_free(sock_ev);
event_base_free(base);//free实例
exit(0);
}
int socket_init()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return sockfd;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd; //获取监听套接子
}
启动 :gcc -o ser ser.c -levent