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 = ;
}

参考:

  1、Libevent初探

  2、Libevent源码

Libevent的IO复用技术和定时事件原理的更多相关文章

  1. Select、Poll、Epoll IO复用技术

    简介 目前多进程方式实现的服务器端,一次创建多个工作子进程来给客户端提供服务, 但是创建进程会耗费大量资源,导致系统资源不足 IO复用技术就是让一个进程同时为多个客户端端提供服务 IO复用技术 之 S ...

  2. Redis03——Redis之单线程+多路IO复用技术

    Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也 ...

  3. Linux网络编程-IO复用技术

    IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...

  4. IO复用三种方式

    简介 IO复用技术,简单来说就是同时监听多个描述符.在没有用到IO复用以前,只能是一个线程或一个 线程去监听,服务端同时有多个连接的时候,需要创建多个线程或者进程.而且,并不是所有的连 接是一直在传输 ...

  5. 简单聊下IO复用

    没图,不分析API Java中IO API的发展:Socket -> SocketChannel -> AsynchronousSocketChannelServerSocket -> ...

  6. 7.3 5种IO模型与IO复用

    5种IO模型分别如下: 1.阻塞IO模型 当上层应用app1调用recv系统调用时,如果对等方没有发送数据(缓冲区没有数据),上层app1将阻塞(默认行为,被linux内核阻塞). 当对等方发送了数据 ...

  7. 可扩展的事件复用技术:epoll和kqueue

    通常来说我喜欢Linux更甚于BSD系统,但是我真的想在Linux上拥有BSD的kqueue功能. 什么是事件复用技术 假设你有一个简单的web服务器,并且那里已经打开了两个socket连接.当服务器 ...

  8. libevent 网络IO分析

    libevent 网络IO分析 Table of Contents 1. 简介 2. 简单使用与入门 2.1. 定时器-timeout 超时回调 2.2. 信号事件 2.3. 读取 socket 3. ...

  9. 高级IO复用应用:聊天室程序

    简单的聊天室程序:客户端从标准输入输入数据后发送给服务端,服务端将用户发送来的数据转发给其它用户.这里采用IO复用poll技术.客户端采用了splice零拷贝.服务端采用了空间换时间(分配超大的用户数 ...

随机推荐

  1. 初探asp.net异步编程之await

    终于毕业了,也顺利进入一家期望的旅游互联网公司.27号入职.放肆了一个多月没写代码,好方啊. 另外一下观点均主要针对于await. 请先看这段话,来自async in C# 5.0.  接下来几个月的 ...

  2. Unity3D 5.x 简单实例 - 发射炮弹

    1,下载.安装: http://unity3d.com/cn/get-unity/download/archive 建议直接借助 UnityDownloadAssistant 进行安装,根据需要勾选需 ...

  3. Oracle内置函数:时间函数,转换函数,字符串函数,数值函数,替换函数

    dual单行单列的隐藏表,看不见 但是可以用,经常用来调内置函数.不用新建表 时间函数 sysdate 系统当前时间 add_months 作用:对日期的月份进行加减 写法:add_months(日期 ...

  4. python 优矿自动化交易

    一.进入官网,打开notebook 自己新建 notebook 二.在代码中编写自己的交易策略 https://uqer.io/help/faqApi/#account相关属性  在帮助文档中可以找到 ...

  5. JavaScript高阶函数

    所谓高阶函数(higher-order function) 就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数. 下面的例子接收两个函数f()和g(),并返回一个新的函数用以计算f(g ...

  6. 前端工程师如何快速的开发一个微信JSSDK应用

    亲们,订阅号出来已经很久了,作为一个前端工程师或者全栈工程师,你是不是错过了什么?大概许多攻城狮同砚还没有反应过来订阅号怎么回事,就马上要被微信的应用号秀一脸了.在应用号还没有正式出来之前,我们赶紧一 ...

  7. 使用 Sticky-Kit 实现基于 jQuery 的元素固定效果

    元素固定效果在网页中应用得很多,比较常见的使用场景有改进导航,显示广告.Sticky-Kit 是一个非常方便的 jQuery 插件,简化了创建/管理粘元素,有复杂的使用功能.这些功能包括:处理多个固定 ...

  8. JS高程3.基本概念(3)

    1.ECMAScript数值的范围 由于内存的限制,在大多数浏览器中,ECMAScript能够拿保存的数据的范围是 5e-324 ~ 1.7976931348623157e+308,其中最小的数值保存 ...

  9. 简单介绍JSON

    如下图:

  10. [JS]笔记14之事件委托

    -->什么是事件委托-->事件委托原理-->事件委托优点-->事件源 / 目标源 一.什么是事件委托 通俗的讲,onclick,onmouseover,onmouseout等这 ...