前言
libevent是一个用C语言编写的,轻量级的开源高性能事件通知库,主要有以下几个特点:事件驱动,高性能,轻量级,专注于网络,支持多平台,支持多种I/O复用技术,select,poll,epoll等,支持I/O定时器和信号事件,注册事件优先级
什么叫I/0多路复用
下面举一个例子,模拟一个tcp服务器处理30个客户socket。
假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:
- 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。
这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。 - 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。
- 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。
- 这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。
转自 链接:https://www.zhihu.com/question/28594409/answer/52835876
其实I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。
在同一个线程里面,通过拨动开关的方式,来同时传输多个I/0流,就是epoll会将他们监视起来,然后就像波动开关一样,谁有数据就拨向谁
libevent的地基event_base
在使用libevent这个库的时候,就像我们盖房子一样,需要一个地基,我们这个接口就是来完成这个工作的,在他的基础上会有的一个事件集合去检查哪一个事件是激活的
struct event_base * event_base_new(void);
申请到的指针可以通过event_base_free释放
event_base_free(struct event_base *)
如果fork出子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化,函数如下:
int event_reinit(struct event_base *base);
等待事件产生,循环等待event_loop
他类似于while(1)的功能,去循环监视事件的发生
int event_base_dispatch(struct event_base *base);
调用该函数,相当于没有设置标志位的event_base_loop。程序将会一直运行,直到没有需要检测的事件了,或者被结束循环的api终止。
int event_base_dispatch(struct event_base *base);
调用该函数,相当于没有设置标志位的event_base_loop。程序将会一直运行,直到
没有需要检测的事件了,或者被结束循环的api终止。
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
struct timeval {
long tv_sec;
long tv_usec;
两个函数的区别是如果正在执行激活事件的回调函数,那么event_base_loopexit
将在事件回调执行结束后终止循环(如果tv时间非NULL,那么将等待tv设置的时间
后立即结束循环),而event_base_loopbreak会立即终止循环。
事件驱动-event
我们来说一下上面的图,主要是几个状态,
- 在创建event *指针的时候是无效的
- 未决态,相当于创建了事件,但是事件没用处于被监听的状态
- 未决,就是事件已经监视,还没有发生
- 激活,事件已经发生
struct event *event_new(struct event_base *base, evutil_socket_t fd,
short events, event_callback_fn cb, void *arg);
event_new负责新创建event结构指针,同时指定对应的地基base,还有对应的文件描述符,
事件,以及回调函数和回调函数的参数。参数说明:
base 对应的根节点
fd 要监听的文件描述符
events 要监听的事件
#define EV_TIMEOUT 0x01 //超时事件
#define EV_READ 0x02 //读事件
#define EV_WRITE 0x04 //写事件
#define EV_SIGNAL 0x08 //信号事件
#define EV_PERSIST 0x10 //周期性触发
#define EV_ET 0x20 //边缘触发,如果底层模型支持
cb 回调函数,原型如下:
typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
arg 回调函数的参数
7. int event_add(struct event *ev, const struct timeval *timeout);
将非未决态事件转为未决态,相当于调用epoll_ctl函数,开始监听事件是否产生。
参数说明:
Ev 就是前面event_new创建的事件
Timeout 限时等待事件的产生,也可以设置为NULL,没有限时。
8. int event_del(struct event *ev);
将事件从未决态变为非未决态,相当于epoll的下树(epoll_ctl调用EPOLL_CTL_DEL操作)操作。
9. void event_free(struct event *ev);
释放event_new申请的event节点。
服务器端简单的实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <event2/event.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
struct event * connev[10];
void readcb (evutil_socket_t fd,short events,void *arg)
{
int num=(int) arg;
int n;
char buf[1024];
bzero(buf,sizeof(buf));
n=read(fd,buf,sizeof(buf));
if (n<=0)
{
close(fd);
event_del(connev[num]);
return;
}
send(fd,buf,n,0);
}
void func(evutil_socket_t fd,short events,void *arg)
{
int num=-1;
struct event_base *base=(struct event_base *) arg;
struct sockaddr_in addr;
int len=sizeof(addr);
//阻塞接受
int new=accept(fd,(struct sockaddr*)&addr,&len);
num++;
if (new>0)
{
connev[num] =event_new (base,new,EV_READ|EV_PERSIST,readcb,(int*)num);
if (connev ==NULL)
{
exit(0);
}
event_add (connev[num],NULL);
puts("3");
}
}
int main(int argc, char *argv[])
{
int sfd=socket(AF_INET,SOCK_STREAM,0);
if (sfd<0)
{
perror ("socket");
exit(-1);
}
int opt=1;
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//ip配置
struct sockaddr_in ip;
ip.sin_family=AF_INET;
ip.sin_port =htons(10088);
ip.sin_addr.s_addr =htonl (INADDR_ANY);
socklen_t len=sizeof(ip);
if (bind(sfd,(struct sockaddr*)&ip,len))
{
perror ("bind");
exit (-1);
}
listen(sfd,5);
//获取地基
struct event_base *base =event_base_new();
if (base==NULL)
{
printf("error base\n");
exit(-1);
}
struct event *ev=event_new(base,sfd,EV_READ|EV_PERSIST,func,base);
if (ev==NULL)
{
printf ("event\n");
exit(-1);
}
//将事件添加到地基上,并调用内部的回调函数
event_add(ev,NULL);
//进入循环
event_base_dispatch(base);
//free
event_base_free(base);
event_free(ev);
close(sfd);
return 0;
}