Libevent的IO复用技术和定时事件原理
Libevent 是一个用C语言编写的、轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
1 Libevent中的epoll
Libevent重要的底层技术之一就是IO复用函数,比如Linux的epoll、windows下的select。Libevent的epoll相关的函数在epoll.c文件中,为了方便使用epoll对事件的操作,定义了一个epollop结构体。
struct epollop {
struct epoll_event *events;
int nevents;
int epfd;
};
其中,events指针用于存放就绪的事件,也就是内核会拷贝就绪的事件到这个events指向的内存中;nevents表示events指向的内存为多大,也就是可以存放多少个epoll_event类型的数据;epfd也就是调用epoll_create()返回的内核事件表对应的描述符。
Libevent为了封装IO复用技术,定义了一个统一的事件操作结构体eventop:
/** Structure to define the backend of a given event_base. */
struct eventop {
/* 后端IO复用技术的名称 */
const char *name; void *(*init)(struct event_base *);
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*dispatch)(struct event_base *, struct timeval *); /* 释放IO复用机制使用的资源 */
void (*dealloc)(struct event_base *); int need_reinit; /* IO复用机制支持的一些特性,可选如下3个值的按位或:EV_FEATURE_ET(支持边沿触发事件EV_ET)、
* EV_FEATURE_O1(事件监测算法的复杂度为O(1))和EV_FEATURE_FDS(不仅能监听socket上的事件,还能
* 监听其他类型的文件描述符上的事件) */
enum event_method_feature features; /* 有些IO复用机制需要为每个IO事件队列和信号事件队列分配额外的内存,以避免同一个文件描述符被重复
* 插入IO复用机制的事件表中。evmap_io_add(或evmap_io_del)函数最亲爱调用eventop的add(或del)方法时,
* 将这段内存的起始地址作为第5个参数传递给add(或del)方法。fdinfo_len指定了该段内存的长度 */
size_t fdinfo_len;
};
对于epoll来说,封装的事件操作为:
const struct eventop epollops = {
"epoll",
epoll_init,
epoll_nochangelist_add,
epoll_nochangelist_del,
epoll_dispatch,
epoll_dealloc,
, /* need reinit */
EV_FEATURE_ET|EV_FEATURE_O1, };
结构体中的函数都是在epoll.c中定义好的,并且都是static的,但是只需要并且也只能通过epollops变量来调用这些函数了,epoll相关的函数就不在赘述,详情可以参考源代码。那么Libevent是什么时候来获取这个变量的值呢?秘密就在event.c文件中,其中定义的eventops数组包含了支持的所有IO复用技术,当然包括我们讲的epoll了。
/* Libevent通过遍历eventops数组来选择其后端IO复用技术,遍历的顺序是从数组的第一个元素开始,
* 到最后一个元组结束。Linux系统下,默认选择的后端IO复用技术是epoll。*/
static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
&evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
&kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
&epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
&devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
&pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
&selectops,
#endif
#ifdef WIN32
&win32ops,
#endif
NULL
};
在event_base的初始化函数event_base_new_with_config中,会遍历eventops数组,选择其中符合要求的IO复用机制,然后退出遍历过程,这样event_base就选择了一个后端的IO复用机制,比如Libevent在Linux下默认是使用epoll的。
for (i = ; eventops[i] && !base->evbase; i++) {
// ... /* also obey the environment variables */
if (should_check_environment &&
event_is_method_disabled(eventops[i]->name))
continue; /* base->evsel记录后端IO复用机制 */
base->evsel = eventops[i]; /* 指向IO复用机制真正存储的数据,它通过evsel成员的init函数来进行初始化 */
/* 比如epoll时,evbase指向的是epollop */
base->evbase = base->evsel->init(base);
}
到这里为止,Libevent已经初始化好了一种后台IO复用机制技术,这里以epoll为例,其他IO复用技术流程也类似。
2 Libevent的定时事件原理
Libevent的定时事件也是要"加入"到Libevent中的IO复用框架中的,比如我们需要定时5秒钟,那么等到5秒钟之后就可以执行对应设置的回调函数了。以下是使用Libevent实现一个简单的定时器应用:
#include <iostream> #include <event.h>
#include <event2/http.h> using namespace std; // Time callback function
void onTime(int sock, short event, void *arg)
{
static int cnt = ;
cout << "Game Over! " << cnt++ << endl; struct timeval tv;
tv.tv_sec = ;
tv.tv_usec = ;
if (cnt < ) {
// Add timer event
event_add((struct event *) arg, &tv);
}
else {
cout << "onTime is over" << endl;
}
} int main(int argc, char **argv)
{
cout << event_get_version() << endl; struct event_base *base = event_init();
struct event ev; evtimer_set(&ev, onTime, &ev); struct timeval timeevent;
timeevent.tv_sec = ;
timeevent.tv_usec = ; event_add(&ev, &timeevent); // Start event loop
event_base_dispatch(base);
event_base_free(base); return ;
}
定时器事件会被加入到一个时间堆(小堆结构)中,每次执行事件等待函数时,对于epoll来说就是epoll_wait函数了,把时间堆上最小节点的时间值赋值给该函数,这样如果有事件来临或者是时间超时了,都会返回。然后判断当前时间和调用事件等待函数之前的时间差是否大于或等于时间堆上最小节点的时间值,如果条件成立就执行对应的时间回调函数,这样就完成了一个定时事件。下面代码就是在事件监听循环中的部分代码。
while (!done) {
base->event_continue = ; /* Terminate the loop if we have been asked to */
if (base->event_gotterm) {
break;
} if (base->event_break) {
break;
} timeout_correct(base, &tv); /* 校准系统时间 */ tv_p = &tv;
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
/* 获取时间堆上堆顶元素的超时值,即IO复用系统调用本次应该设置的超时值 */
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
/* 如果有就绪事件尚未处理,则将IO复用系统调用的超时时间置0
* 这样IO复用系统调用就直接返回,程序也就可以直接处理就绪事件了 */
evutil_timerclear(&tv);
} /* If we have no events, we just exit */
/* 如果event_base中没有注册任何事件,则直接退出事件循环 */
if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = ;
goto done;
} /* update last old time */
gettime(base, &base->event_tv); /* 更新系统时间 */ clear_time_cache(base); /* 调用事件多路分发器的dispatch方法等待事件 */
res = evsel->dispatch(base, tv_p); // 超时时间返回值为0
if (res == -) {
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -;
goto done;
} update_time_cache(base); /* 将系统缓存更新为当前系统时间 */ timeout_process(base); /* 检查时间堆上的到期事件并以此执行之 */ if (N_ACTIVE_CALLBACKS(base)) {
/* 调用event_process_active函数依次处理就绪的信号事件和IO事件 */
/* 这里也可能有定时事件 */
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) ==
&& n != )
done = ;
} else if (flags & EVLOOP_NONBLOCK)
done = ;
}
参考:
Libevent的IO复用技术和定时事件原理的更多相关文章
- Select、Poll、Epoll IO复用技术
简介 目前多进程方式实现的服务器端,一次创建多个工作子进程来给客户端提供服务, 但是创建进程会耗费大量资源,导致系统资源不足 IO复用技术就是让一个进程同时为多个客户端端提供服务 IO复用技术 之 S ...
- Redis03——Redis之单线程+多路IO复用技术
Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也 ...
- Linux网络编程-IO复用技术
IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...
- IO复用三种方式
简介 IO复用技术,简单来说就是同时监听多个描述符.在没有用到IO复用以前,只能是一个线程或一个 线程去监听,服务端同时有多个连接的时候,需要创建多个线程或者进程.而且,并不是所有的连 接是一直在传输 ...
- 简单聊下IO复用
没图,不分析API Java中IO API的发展:Socket -> SocketChannel -> AsynchronousSocketChannelServerSocket -> ...
- 7.3 5种IO模型与IO复用
5种IO模型分别如下: 1.阻塞IO模型 当上层应用app1调用recv系统调用时,如果对等方没有发送数据(缓冲区没有数据),上层app1将阻塞(默认行为,被linux内核阻塞). 当对等方发送了数据 ...
- 可扩展的事件复用技术:epoll和kqueue
通常来说我喜欢Linux更甚于BSD系统,但是我真的想在Linux上拥有BSD的kqueue功能. 什么是事件复用技术 假设你有一个简单的web服务器,并且那里已经打开了两个socket连接.当服务器 ...
- libevent 网络IO分析
libevent 网络IO分析 Table of Contents 1. 简介 2. 简单使用与入门 2.1. 定时器-timeout 超时回调 2.2. 信号事件 2.3. 读取 socket 3. ...
- 高级IO复用应用:聊天室程序
简单的聊天室程序:客户端从标准输入输入数据后发送给服务端,服务端将用户发送来的数据转发给其它用户.这里采用IO复用poll技术.客户端采用了splice零拷贝.服务端采用了空间换时间(分配超大的用户数 ...
随机推荐
- 百度编辑器UEditor常用设置函数大全
在线文档对UEditor说明不够全面,收集了一些常用的方法和基本设置,以供参考.1.创建编辑器UE.getEditor('editor', { initialFrameWidth:"100% ...
- C# 判断字符串是否是int/double
using System.Text.RegularExpressions; /// <summary> /// 判断字符串是否是int/double /// </summary> ...
- Xenocode Postbuild 2010 for .NET 使用说明
文章转载自网络 用法一: .导入要加密的dotNET程序或assembly文件(.dll/.exe) .选择第二个选项卡“Protect” .点击“Select Pattern” .选中所有“Obje ...
- [WCF编程]8.服务实例的生命周期
一.服务实例的生命周期概览 我们已经直到,通过显式调用Close方法或等待默认的超时时间到来,都可以释放服务实例.但是,在会话连接里,经常需要按一定顺序调用方法. 二.分步操作 会话契约的操作有时隐含 ...
- 为input输入框添加圆角并去除阴影
<input type="text" name="bianhao" value="" placeholder="请输入商品编 ...
- virtualbox 虚拟机Ubuntu 传文件-共享
- SpringMVC的注解开发入门
1.Spring MVC框架简介 支持REST风格的URL 添加更多注解,可完全注解驱动 引入HTTP输入输出转换器(HttpMessageConverter) 和数据转换.格式化.验证框架无缝集成 ...
- java静态方法调用&&构造函数&&静态块
静态方法,也就是使用static声明的方法,在虚拟机启动加载类的时候就进行了创建,所以使用到静态方法时,直接使用类名点静态方法即可调用.java在执行静态方法前,不会调用构造函数:构造函数是在实例化j ...
- Docker化运维方式讲解
应用迁移需求 应用运维需要考虑的一个重要问题就是迁移, 在不同机器.机房.环境间迁移.迁移的原因有很多, 比如硬件过保(硬件故障), 机房迁移, 应用扩缩容等. 应用迁移的核心需求是: 简单.迁移操作 ...
- easyui框架对tab的限制提示
使用easyui框架如果页面打开太多可能导致页面加载缓慢的问题,下面我们看看代码怎么写: var $tab = $('#tabs'); var tabCount = $tab.tabs('tabs') ...