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 面向接口可扩展框架之使用“类型转化基础服务”测试四种Mapper(AutoMapper、EmitMapper、NLiteMapper及TinyMapper)

    Asp.net 面向接口可扩展框架的“类型转化基础服务”是我认为除了“核心容器”之外最为重要的组成部分 但是前面博文一出,争议很多,为此我再写一篇类型转化基础服务和各种Mapper结合的例子,顺便对各 ...

  2. Java日期时间操作的一些方法

    1. 获得Calendar实例: Calendar c = Calendar.getInstance(); 2. 定义日期/时间的格式: SimpleDateFormat sdf =new Simpl ...

  3. luogg_java学习_13_GUI

    本文为博主辛苦总结,希望自己以后返回来看的时候理解更深刻,也希望可以起到帮助初学者的作用. 转载请注明 出自 : luogg的博客园 谢谢配合! GUI 容器 JFrame , JPanel , JS ...

  4. zigbee 路由节点丢失后清除 该节点的残余网络信息

    清除脱离网络的 路由节点(stale device)的 残留在各表中以AssociationDevList为例的残余信息. 如图所示拓扑结构中: 路由器1脱离网络后,通过协调器按键操作来  清除 协调 ...

  5. C++11之for循环的新用法

    C++使用如下方法遍历一个容器: #include "stdafx.h" #include<iostream> #include<vector> int m ...

  6. linux 共享内存 shmat,shmget,shmdt,shmctl

    shmget int shmget(key_t key, size_t size, int flag);//开辟一段共享内存 key_t key :标识符的规则() size_t size :共享内存 ...

  7. 用Java语言编写一个简易画板

    讲了三篇概博客的概念,今天,我们来一点实际的东西.我们来探讨一下如何用Java语言,编写一块简易的画图板. 一.需求分析 无论我们使用什么语言,去编写一个什么样的项目,我们的第一步,总是去分析这个项目 ...

  8. 时钟周期,CPU周期,指令周期,CPU时间片

    从小到大来说:时钟周期,CPU周期,指令周期,CPU时间片 时钟周期:一个脉冲需要的时间,频率的倒数 CPU周期:读取一个指令节所需的时间 指令周期:读取并执行完一个指令所需的时间 CPU时间片:CP ...

  9. asp.net mvc 自定义pager封装与优化

    asp.net mvc 自定义pager封装与优化 Intro 之前做了一个通用的分页组件,但是有些不足,从翻页事件和分页样式都融合在后台代码中,到翻页事件可以自定义,再到翻页和样式都和代码分离, 自 ...

  10. arcgis server之路网服务发布

    路网服务发布首先需要建立好道路的网络集,为了保证道路网络分析的准确性,建立网络集之前,要对道路图层进行拓扑差错,确保道路的连通性.具体操作流程为:道路拓扑差错-建立几何网络集-路网服务发布. 1.道路 ...