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. xhtmlConformance与xhtml脚本呈现

    此配置节只有一个属性——mode,该特性为 ASP.NET 应用程序指定 XHTML 呈现模式.它包含三个值 要让此配置生效,需要把<pages>配置节中的controlRendering ...

  2. redis+cookies实现session机制(解决 手机浏览器不自动回传cookies导致session不可用问题)

    昨天在手机端测试自己的项目遇到如下情况. 1.在手机上(苹果qq浏览器),登陆时存在session中的图片验证码结果,一直获取不到,考虑是cookies的问题.但是其他网站有貌似可以正常使用cooki ...

  3. perl use FileHandle;打开多个文件

    use FileHandle;my %fh; my @filehandlename=("A","B","C"); ##文件句柄的名字: fo ...

  4. 《C#微信开发系列(3)-获取接口调用凭据》

    3.0获取接口调用凭据 ①接口说明 access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token.开发者需要进行妥善保存.access_token的存储至少要保留 ...

  5. 【JavaScript】浅析javaScript和HTML与unicode字符集的关系

    目录结构: // contents structure [-] javaScript和HTML的字符集 javaScript和HTML如何表现unicode字符集 参考文章 javaScript和HT ...

  6. angular源码分析:$compile服务——指令的编写

    这一期中,我不会分析源码,只是翻译一下"https://docs.angularjs.org/api/ng/service/$compile",当然不是逐字逐句翻译,讲解指令应该如 ...

  7. 原生JS:Function对象(apply、call、bind)详解

    Function对象(apply.call.bind) 原创文章,转摘请注明出处:苏福:http://www.cnblogs.com/susufufu/p/5850180.html 本文参考MDN做的 ...

  8. SharePoint 2013 对二进制大型对象(BLOB)进行爬网

    本文是参考MSDN文档做的示例,SharePoint 2013搜索二进制对象(BLOB),通过外部内容类型的方式将外部数据与SharePoint相关联,修改BCD模型,使SharePoint能够爬网外 ...

  9. 使用DOTNETZIP过滤并压缩相对目录

    业务要求: 压缩某个文件夹及其子目录 压缩时只压缩指定的文件类型,如cshtml 压缩后保持相对目录   找了很久,没有直接的DEMO,最后尝试通过以下代码完成 示例演示了只压缩cshtml和js,同 ...

  10. C语言链表实现约瑟夫环问题

    需求表达:略 分析: 实现: #include<stdio.h> #include<stdlib.h> typedef struct node { int payload ; ...