Libevent初探

  Libevent 是一个用C语言编写的、轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。

  Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。Libevent之于C语言网络编程,类似于Nettty之于Java Web编程。学习Netty的小伙伴,不防看下Libevent的实现,会加深对Netty框架的理解~

  Libevent的安装教程网上较多,LZ在此就不再赘述,下面直接来点干货-Libevent如何使用。

检查Libevent支持的IO复用方法

  Libevent作为一个高性能网络库,内部封装了多种IO复用技术,如果想看下Libevent在当前系统下支持哪些IO复用技术呢?

int main(int argc, char **argv)
{
    // 版本信息
    cout << event_get_version() << endl;

    // 所支持的IO复用方法
    const char **methods = event_get_supported_methods();
    for (int i = 0; methods[i] != NULL; i++) {
        cout << methods[i] << endl;
    }

    return 0;
}

输出结果为:(Centos7 Clion 2016.1.3环境)

  event_get_supported_methods()函数返回Libevent支持的IO复用方法名称数组,以NULL结尾。该函数实际返回的是全局变量eventops数组,eventops数组存放的是所有支持的IO复用函数,eventops声明部分的代码如下:

/* Array of backends in order of preference. */
/* 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
};

Libevent是如何打日志的

  libevent的错误处理底层调用的是va_start/va_end等相关宏,它们所在的头文件是<stdarg.h>,使用C函数库提供的这些函数,我们也可以实现一个自己的打日志程序,以下是一个使用va_start/va_end的测试程序:

void log(const char *fmt, ...)
{
    char buff[512];
    va_list ap;

    va_start(ap, fmt);
    int len = vsnprintf(buff, sizeof(buff), fmt, ap);
    buff[len] = ‘\0‘;
    va_end(ap);

    cout << buff << endl;
}
  • va_start:宏定义,引用最后一个固定参数所以它能够对可变参数进行定位。
  • va_end:宏定义,函数返回之前一定要调用va_end,这是因为某些实现在函数返回之前需要调整控制信息。

  使用上述函数,我们就可以愉快地打日志了,比如按照如下形式来调用:

log("hi, are you %s?", "luxon28");
log("name=%s, age=%d", "luoxn28", 23);

  更多va_start/va_end信息请点击:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html

定时器的使用

#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 = 0;
    cout << "Game Over! " << cnt++ << endl;

    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    if (cnt < 5) {
        // 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 = 1;
    timeevent.tv_usec = 0;

    event_add(&ev, &timeevent);

    // Start event loop
    event_base_dispatch(base);
    event_base_free(base);

    return 0;
}

输出结果如下:

  LZ安装的是Libevent版本是2.0版本,event_init()函数初始化一个事件类结构体,其中已经选择好了IO复用函数,比如Linux下一般是epoll;初始化了一个事件活动队列,当事件发生时,会被加入到该事件活动队列中,然后统一执行事件活动队列中的所有事件(也就是调用对应的回调函数)。event_base结构体详细内容如下:

/* 结构体event_base是Libevent的Reactor */
struct event_base {
    /* 初始化Reactor时选择的一种后端IO复用机制,并记录在如下字段中 */
    const struct eventop *evsel;
    /* 指向IO复用机制真正存储的数据,它通过evsel成员的init函数来进行初始化 */
    void *evbase;

    /* 事件变化队列,其用途是:如果一个文件描述符上注册的事件被多次修改,则可以使用缓冲区来避免重复的
     * 系统调用(比如epoll_wait)。它仅能用于时间复杂度为O(1)的IO复用技术 */
    struct event_changelist changelist;

    /* 指向信号的后端处理机制,目前仅在singal.h文件中定义了一种处理方法 */
    const struct eventop *evsigsel;
    /* 信号事件处理器使用的数据结构,其中封装了一个由socketpair创建的管道。它用于信号处理函数和
     * 事件多路分发器之间的通信 */
    struct evsig_info sig;

    /* 以下3个成员是添加到该event_base的虚拟事件、所有事件和激活事件的数量 */
    int virtual_event_count;
    int event_count;
    int event_count_active;

    /* 是否执行完活动事件队列上的剩余的任务之后就退出事件处理 */
    int event_gotterm;
    /* 是否立即退出事件循环,而不管是否还有任务需要处理 */
    int event_break;
    /* 是否应该启动一个新的事件循环 */
    int event_continue;

    /* 目前正在处理的活动事件队列的优先级 */
    int event_running_priority;

    /* 事件循环是否启动 */
    int running_loop;

    /* 活动事件队列数组,索引值越小的队列,优先级越高。高优先级的活动事件队列中的事件处理器将被优先处理 */
    struct event_list *activequeues;
    /* 活动事件队列数组的大小,即该event_base共有nactivequeues个不同优先级的活动事件队列 */
    int nactivequeues;

    /* common timeout logic */

    /* 以下3个成员用于管理通用定时器队列 */
    struct common_timeout_list **common_timeout_queues;
    int n_common_timeouts;
    int n_common_timeouts_allocated;

    /* 存放延时回调函数的链表,事件循环每次成功处理完一个活动事件队列中的所有事件之后,
     * 就调用一次延迟回调函数 */
    struct deferred_cb_queue defer_queue;

    /* 文件描述符和IO事件之间的映射关系表 */
    struct event_io_map io;

    /* 信号值和信号事件之间的映射关系表 */
    struct event_signal_map sigmap;

    /* 注册事件队列,存放IO事件处理器和信号事件处理器 */
    struct event_list eventqueue;

    struct timeval event_tv;

    /* 时间堆 */
    struct min_heap timeheap;

    struct timeval tv_cache;

#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
    /** Difference between internal time (maybe from clock_gettime) and
     * gettimeofday. */
    struct timeval tv_clock_diff;
    /** Second in which we last updated tv_clock_diff, in monotonic time. */
    time_t last_updated_clock_diff;
#endif

    /* 多线程支持 */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
    /* threading support */
    /** The thread currently running the event_loop for this base */
    /* 当前运行该event_base的事件循环的线程 */
    unsigned long th_owner_id;
    /** A lock to prevent conflicting accesses to this event_base */
    void *th_base_lock;            /* 锁变量 */
    /** The event whose callback is executing right now */
    /* 当前事件循环正在执行哪个事件处理器的回调函数 */
    struct event *current_event;
    /** A condition that gets signalled when we‘re done processing an
     * event with waiters on it. */
    /* 条件变量,用于唤醒正在等待某个事件处理完毕的线程 */
    void *current_event_cond;
    /** Number of threads blocking on current_event_cond. */
    int current_event_waiters;    /* 等待current_event_cond的线程数 */
#endif

    /** Flags that this base was configured with */
    /* 该vent_base的一些配置参数 */
    enum event_base_config_flag flags;

    /* 下面这些成员变量给工作线程唤醒主线程提供了方法(使用socketpair创建的管道) */
    /* Notify main thread to wake up break, etc. */
    /** True if the base already has a pending notify, and we don‘t need
     * to add any more. */
    int is_notify_pending;
    /** A socketpair used by some th_notify functions to wake up the main
     * thread. */
    evutil_socket_t th_notify_fd[2];
    /** An event used by some th_notify functions to wake up the main
     * thread. */
    struct event th_notify;
    /** A function used to wake up the main thread from another thread. */
    int (*th_notify_fn)(struct event_base *base);
}

  event_add()函数是往事件结构体中加入监听的一个事件,这里是定时事件,当定时事件到时,就会执行对应的回调函数。event_base_dispatch()函数开始执行事件监听,对应于epoll的话也就是调用epoll_wait了。最后,当程序执行完毕后,需要调用event_base_free()函数来执行资源的销毁操作,至此,整个定时器事件就执行完毕了。

简单的HTTP服务器

  使用Libevent,我们可以用不超过50行代码实现一个简单的HTTP服务器程序,没有听错,就是几十行代码,不像Java那样,需要配置Tomcat,然后编写对应的Servet,配置web.xml等等(如果使用SSM或者SSH的话步骤或许更多一点呦 :( )。下面就是一个简单的HTTP服务器示例代码:

#include <iostream>

#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>

using namespace std;

#define INFO 1
#define ERR  3
static void log(int level, string info)
{
    switch (level) {
        case INFO:
            cout << "[info] tid[" << pthread_self() << "]: " << info << endl;
            break;
        case ERR:
            cout << "[err] tid[" << pthread_self() << "]: " << info << endl;
            break;
        default:
            break;
    }
}

/**
 * http callback function
 */
void httpHandler(struct evhttp_request *request, void *arg) {
    struct evbuffer *buff = evbuffer_new();
    if (!buff) {
        log(INFO, "evbuffer_new error");
        return;
    }

    evbuffer_add_printf(buff, "Hello world</br>");
    evbuffer_add_printf(buff, "Server Responsed.</br> Requested: %s<br/>", evhttp_request_get_uri(request));
    evbuffer_add_printf(buff, " Host: %s<br/>", evhttp_request_get_host(request));
    evbuffer_add_printf(buff, " Command: %d", evhttp_request_get_command(request));
    evhttp_send_reply(request, HTTP_OK, "OK", buff);
    evbuffer_free(buff);
}

int main(int argc, char **argv)
{
    struct event_base *base = event_base_new();
    struct evhttp *httpServer = evhttp_new(base);

    int result = evhttp_bind_socket(httpServer, NULL, 8080);
    if (result != 0) {
        log(ERR, "evhttp_bind_socket error");
        return -1;
    }

    /* 这是http回调函数 */
    evhttp_set_gencb(httpServer, httpHandler, NULL);
    cout << "Http server start OK..." << endl;

    event_base_dispatch(base);

    evhttp_free(httpServer);
    event_base_free(base);

    return 0;
}

  访问页面如下,192.168.1.150主机是linux服务器。

  看到这里,学习Java Web的小伙伴是不是觉得很熟悉,没错,就是像Servlet。LZ个人觉得,对于小型程序来说,使用C/C++的网络库编程程序更爽一点,因为更加"接地气 "一点,也就操作起来更加灵活,使用Java的话肯定要使用Servet容器了,比如Tomcat或者Jboss等,然后各种配置等。但是对于动态Web技术来说,使用Java更爽一点。

参考资料

  1、libevent-百度百科

  2、Libevent部分源码

时间: 2024-10-15 07:36:03

Libevent初探的相关文章

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重

libevent之Reactor模式

通过前边的一篇博文轻量级网络库libevent初探,我们知道libevent实际上是封装了不同操作系统下的/dev/poll.kqueue.event ports.select.poll和epoll事件机制,从而给我们提供一个统一的接口. libevent采用了Reactor I/O 设计模式,而Reactor是基于同步I/O机制的,所以libevent实际是一个基于同步I/O机制的库. 对于I/O设计模式,与Reactor相对应的还有Proactor.下边我们先来看下这两者的不同之处. Rea

利用epoll写一个&quot;迷你&quot;的网络事件库

epoll是linux下高性能的IO复用技术,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了.epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存I

Key/Value之王Memcached初探:一、掀起MC的盖头来

一.Memcached是何方神圣? 在数据驱动的Web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的HttpRuntime.Cache虽然已经可以实现对页面局部进行缓存,但还是不够灵活,此时Memcached或许是你想要的. Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.经过多年的发

Windows 上静态编译 Libevent 2.0.10 并实现一个简单 HTTP 服务器(无数截图)

[文章作者:张宴 本文版本:v1.0 最后修改:2011.03.30 转载请注明原文链接:http://blog.s135.com/libevent_windows/] 本文介绍了如何在 Windows 操作系统中,利用微软 Visual Studio 2005 编译生成 Libevent 2.0.10 静态链接库,并利用 Libevent 静态链接库,实现一个简单的 HTTP Web服务器程序:httpd.exe. 假设 Visual Studio 2005 的安装路径为“D:\Program

进阶之初探nodeJS

一.前言 在"初探nodeJS"随笔中,我们对于node有了一个大致地了解,并在最后也通过一个示例,了解了如何快速地开启一个简单的服务器. 今儿,再次看了该篇随笔,发现该随笔理论知识稍多,适合初级入门node,固萌生一个想法--想在该篇随笔中,通过一步步编写一个稍大一点的node示例,让我们在整体上更加全面地了解node. so,该篇随笔是建立在"初探nodeJS"之上的,固取名为"进阶之初探nodeJS". 好了,侃了这多,那么我们即将实现一个

1 Linux 安装 Libevent

Libevent官网:http://libevent.org/ 在线文档:http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/ 下载地址:https://github.com/libevent/libevent/releases/ 解压,编译,安装 创建文件夹 [email protected]:~$ mkdir libevent [email protected]:~$ cd libevent/ 解压文件: [email protec

浅谈libevent的使用--事件和数据缓冲

首先在学习libevent库的使用前,我们还要从基本的了解开始,已经熟悉了epoll以及reactor,然后从event_base学习,依次学习事件event.数据缓冲Bufferevent和数据封装evBuffer等,再结合具体的几个实例来了解libevent库的一些基本使用,有助于我们理解它的一些内部实现(由于之前我已经写过一篇epoll反应堆模型的,所以这里就不再介绍,直接从event_base开始介绍). libevent下载与安装: 在官网上找到 libevent-2.0.22-sta

从273二手车的M站点初探js模块化编程

前言 这几天在看273M站点时被他们的页面交互方式所吸引,他们的首页是采用三次加载+分页的方式.也就说分为大分页和小分页两种交互.大分页就是通过分页按钮来操作,小分页是通过下拉(向下滑动)时异步加载数据. 273这个M站点是产品推荐我看的.第一眼看这个产品时我就再想他们这个三次加载和翻页按钮的方式,那么小分页的pageIndex是怎么计算的.所以就顺便看了下源码. 提到看源码时用到了Chrome浏览器的格式化工具(还是朋友推荐我的,不过这个格式化按钮的确不明显,不会的话自行百度). 三次加载和分