libevent的使用
8-1 安装
自己百度一下,安装它不是特别难,加油!!!
8-2 libevent介绍
它是一个开源库,用于处理网络和定时器等等事件。它提供了跨平台的API,能够在不同的操作系统上实现高性能,可扩展的世界去的编程。
1.事件驱动:libevent使用事件驱动模型,通过监听事件的就绪状态来触发相应的回调函数。(事件:网络I/O,信号,定时器等等)。
2.跨平台:libevent可以在Linux,Unix,Windows等系统上使用。
3.高性能:libevent底层采用epoll等等实现。并且采用非阻塞I/O技术,实现高效的事件处理。可以处理大量的并发连接和高负载情况。
4.线程安全:libevent提供了安全的api,可以在多线程下使用。不同线程共享一个event_base对象,并且独立地注册和处理事件,保证线程之间的数据安全。
5.定时器支持:libevent提供了定时器功能,可以注册和管理不同的定时器。程序员可以根据指定的定时器的触发时间来实现定时任务的调度和执行。
6.异步DNS解析:可以在不阻塞主程序的情况下进行域名查询操作。
7.可扩展性:允许程序员编写自定义的事件处理机制和回调函数。
总之它很厉害。
8-3 libevent库的使用
首先在程序中包含头文件 #include <event2/event.h>
之后在使用gcc编译的时候要指定一个库 -levent 例如:gcc -o main main.c -levent
8-4 libevent的地基-event_base
在使用libevent前,需要分配一个或者多个event_base结构体,每个结构体都有一个事件集合,可以检测那个事件是激活的。event_base结构体相当于epoll的红黑树根节点,每个结构体都有一种用于检测某种事件已经就绪的方法。
8-4-1创建结构体event_base函数
函数原型:struct event_base *event_base_new(void);
函数功能:创建一个event_base对象,用于管理事件循环,事件注册和分发等。
函数参数:无。
函数返回值:
成功:返回一个event_base类型的结构体指针。
失败:返回NILL。
例子:struct event_base *base = event_base_new(); //创建一个结构体,并且指针base指向那个结构体。
8-4-2释放结构体event_base_free函数
函数原型:void event_base_free(struct event_base * base);
函数功能:释放一个event_base对象,创建完event_base对象后,如果不用了记得释放掉。
函数参数:一个指向event_base对象的结构体指针。
函数返回值:无。
8-4-3重置结构体event_base函数
函数原型:int event_reinit(struct event_base *base);
函数功能:重置原来的event_base对象
函数参数:一个指向event_base对象的结构体指针。
函数返回值:
成功:返回0。
失败:返回-1。
8-5查看libevent支持那些I/O复用
8-5-1获取当前平台支持的I/O复用方式函数
函数原型:const char **event_get_supported_methods(void);
函数功能:获取当前平台支持的I/O复用方法。
函数参数:无。
函数返回值:返回一个二维数组,将其打印即可得到支持的复用方式。
8-5-2获取当前event_base对象使用的I/O复用方式
函数原型:const char *event_base_get_method(const struct event_base *base);
函数功能:获取当前event_base对象使用的I/O复用方法。
函数参数:一个指向event_base对象的结构体指针。
函数返回值:返回一个字符串指针,打印即可获得想要的结果。
注意:打印字符串的判断字符串结尾的条件是:字符串[i] != NULL
8-6循环等待event_loop(等待事件产生)
在创建libevent的基础之后就需要等待事件的产生,所以程序就不能退出了,类似于之前的while(1)无限循环中不断监听文件描述符的某缓冲区是否有动静。于是libevent中也给出了类似的api接口,可以让我们直接进入循环中然后等待事件激活。
8-6-1事件循环函数event_base_loop函数
函数原型:int event_base_loop(struct event_base *base, int flags);
函数功能:在指定的event_base上运行事件循环,不断检测活动的事件,并且调用相应的回调函数。
函数参数:
event_base:一个指向event_base对象的指针,指明要在那个对象上面运行事件循环。
flage:事件循环的标志位,用于指定事件循环的行为。
EVLOOP_ONCE:仅运行一次事件循环,处理完当前已经就绪的事情后立刻返回,没有事件则阻 塞等待。
EVLOOP_NONBLOCK:非阻塞模式运行事件循环,即使没有任何事件就绪也会立刻返回。
函数返回值:
成功:表示事件循环正常结束,返回0。
失败:返回-1。
8-6-2事件循环函数event_base_dispatch函数
函数原型:int event_base_dispatch(struct event_base *base);
函数功能:在指定的对象上运行事件循环,不断检测活动的事件,并且调用回调函数。该函数默认以阻塞 模式运行。而且是一直循环检测,直到开发者调用相应的api函数,这个循环才会关闭,函数才会返回。
函数参数:一个指向event_base的指针。
函数返回值:
成功:循环正常结束返回0。
失败:返回-1。
8-6-3结束事件循环event_base_loopexit函数(定时结束)
函数原型:int event_base_loopexit(struct event_base *base, const struct timeval *tv);
函数功能:在指定的时间过后立刻退出事件等待循环。
函数参数:
event_base:一个指向event_base对象的指针,用于指定要结束那个对象的事件循环。
tv:一个timeval结构体的指针,用于指定等待时间。如果填NULL则立刻退出循环。
结构体timeval的成员:
long tv_sec; //秒数
long tv_usec; //微妙
函数返回值:成功返回0。 失败返回-1。
8-6-4结束事件循环event_base_loopbreak函数(立刻结束)
函数原型:int event_base_loopbreak(struct event_base);
函数功能:立刻退出事件等待循环。
函数参数:
event_base:一个指向event_base对象的指针,用于指定要结束那个对象的事件循环。
函数返回值:成功返回0 。失败返回-1 。
8-7事件驱动event
8-7-1 event的几种状态
1.无效指针:只是定义了一个event指针struct event *ptr,但未给他赋值,此时指针为无效状态。
2.非未决:当创建了一个event对象,并且调用event_new函数为其分配了内存和初始化相关参数,但没有将其加入到事件循环中监听时,event对象就是非未决状态。
3.未决:当通过event_add函数将event对象注册到event_base对象上进行监听时,event对象处于未决状态。此时event对象将于特定的世界源关联,等待事件的触发。
4.激活:当已注册的事件开始发生,event会进入激活状态。此时libevent会自动调用与该对象关联的回调函数来处理该事件。
8-7-2 自定义回调函数
函数原型:typedef void(*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
函数功能:定义了一个函数指针类型event_callback_fn,它指向一个回调函数,此回调函数在特定事件发生时被调用。
函数参数:
evutil_socket_t fd:表示事件关联的文件描述符。
short events:表示事件的类型。(读事件,写事件)
void *arg:一个指向用户自定义数据的指针。
函数返回值:无。
8-7-3 创建事件对象 event_new函数
函数原型:struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *arg);
函数功能:用于创建一个event对象,并且将event对象注册到event_base对象中去参与事件循环。并且返回该event对象的指针。
函数参数:
struct event_base *base:指向event_base对象的指针。
evutil_socket_t fd:要关联的文件描述符。如果该描述符上发生了事件时,将会触发回调函数。
short events:表示注册的事件类型,例如读事件,写事件,定时器等等。
event_callback_fn cb:事件发生时要调用的回调函数。该回调函数指针遵循event_callback_fn函数指针类 型的定义。
void *arg:传递给回调函数的用户数据指针。
函数返回值:成功则返回一个event对象。失败返回NULL。
8-7-4 常用事件events
EV_TIMEOUT:超时时间。当指定的时间间隔到达时,触发相应的回调函数。
EV_READ:读事件。当一个文件描述符可读时,触发相应的回调函数。
EV_WRITE:写事件。当一个文件描述符可写时,触发相应的回调函数。
EV_SIGNAL:信号事件。当指定的信号发生时,触发相应的回调函数。
EV_PERSIST:周期性触发。当回调函数被触发后,事件任然保持注册状态,可以多次触发。
EV_ET:边缘触发(需要底层模型支持才可以生效)在此模式下,只有文件描述符状态发送变化时才会触发事件。
注意:如果想要设置持续的读写事件可以这样:EV_READ | EV_PERSIST
8-7-5 使用宏定义快速创建信号事件对象
宏定义:#define evsignal_new(b, x, cb, arg) event_bew((b), (x), 事件, (cb), (arg))
宏参数:
(b):指向基础event_base对象的指针。
(x):信号值,表示要监听的信号。
(cb):事件发生时的回调函数。填一个函数指针类型。
(arg):传递给回调函数的用户自定义数据指针。
8-7-6 将事件添加到事件循环的event_add函数
函数原型:int event_add(struct event *ev, const struct timeval *timeout);
函数功能:将事件添加到事件循环中。
函数参数:
ev:指向要添加的事件的指针,并且该事件必须已经被初始化。
timeout:指向结构体struct timeval的指针,表示超时时间,如果填NULL则表示没时间限制。
函数返回值:成功返回0,失败返回-1。
8-7-7 将事件从事件循环中移除的event_del函数
函数原型:int event_del(struct event *ev);
函数功能:从事件循环中移除一个已经添加的事件。
函数参数:ev是指向要删除的事件的指针。
函数返回值:成功则返回0,失败则返回-1。
8-7-8 释放event_new函数创建的event对象所占的内存资源event_free函数
函数原型:void event_free(struct event *ew);
函数功能:释放event_new函数创建的event对象所占的内存资源。
函数参数:ev是指向要释放的event对象。
函数返回值:无。
8-8使用libevent创建服务器的步骤
第一步:创建套接字,并且绑定本地地址,然后监听这个套接字描述符。
第二步:调用event_base_new函数创建一个event_base对象。
第三步:创建event事件,并且设置好相应的事件和回调函数,如果在回调函数中也要访问当前的event_base对象,那么就需要将该对象作为回调函数的参数传入进去。
第四步:调用event_add将该event事件对象加入到event_base对象中(上树)。
第五步:调用event_base_dispatch进入事件循环等待事件的发生。事件发生会自动调用相应的回调函数的。
第六步:如果服务端完成了工作则需要调用event_base_free释放刚才的event_base对象。调用event_free释放刚才的event事件对象。最后关闭套接字描述符。
8-9 服务端代码实例:
点击查看代码
#include <event2/event.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct event* connev = NULL;//全局变量事件
//回调函数
void fun(evutil_socket_t fd, short events, void* arg) {
int n;
char data[128];//接收来自客户端的数据
char serv_data[256] = "服务器已经收到你的来信!";
//接收并且处理数据
n = recv(fd, data, sizeof(data), 0);
if (n < 0) {
printf("接收数据失败!\n");
//记得删除事件
event_del(connev);
close(fd);
}
else {
if (send(fd, serv_data, sizeof(serv_data), 0) == -1) {
printf("数据发送失败\n");
event_del(connev);
close(fd);
}
}
}
//回调函数
void func(evutil_socket_t fd, short events, void* arg) {
//调用func时传入了地基base,然后需要我们将其赋值给func中的base,以便访问
struct event_base* base = (struct event_base*)arg;
//接收新的客户端连接
int cfd = accept(fd, NULL, NULL);
if (cfd < 0) {
printf("建立连接失败!\n");
return;
}
//新建事件,还是以base为地基,然后持续监听与客户端连接的cfd的读缓冲区
//如果有动静则调用func
connev = event_new(base, cfd, EV_READ | EV_PERSIST, fun, NULL);
if (connev == NULL) {
printf("error\n");
//退出事件循环
event_base_loopexit(base, NULL);
}
//添加事件到地基上
event_add(connev, NULL);
}
int main()
{
//创建套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd < 0) {
printf("create socket error!");
exit(0);
}
//设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//绑定本地地址
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));//初始化
serv.sin_addr.s_addr = htonl(INADDR_ANY);
serv.sin_port = htons(10066);
serv.sin_family = AF_INET;
int rent = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
if (rent < 0) {
printf("bind addr error!");
exit(0);
}
//监听文件描述符
rent = listen(lfd, 128);
if (rent < 0) {
printf("listen socket error");
exit(0);
}
//创建地基
struct event_base* base = event_base_new();
if (base == NULL) {
printf("地基创建失败!\n");
exit(0);
}
//创建事件,以base为基础,检测文件描述符lfd的读事件,并且持续检测,如果有动静则启动回调函数func
//并且将地基base传入到func中,这样就可以在回调函数中对其进行操作了
struct event* ev = event_new(base, lfd, EV_READ | EV_PERSIST, func, base);
if (ev == NULL) {
printf("创建事件失败!\n");
exit(0);
}
//将事件加入到地基base中,持续时间这里无限长
event_add(ev, NULL);
//进入事件循环等待事件发生
event_base_dispatch(base);
//释放资源
event_base_free(base);
event_free(ev);
//关闭文件描述符
close(lfd);
return 0;
}