libevent学习笔记

(个人能力有限,翻译疏漏,还望谅解,不喜勿喷)查看官方原文请点击这里

0 Preliminaries

  • 瞥一眼

Libevent是编写快速便捷非阻塞IO的库,有以下几部分组成:
evutil : 通常用来提取不同平台网络实现的区别;
event and event_base : Libevent的核心模块,提供sockets可读写时刻,超时监听,检测操作系统新号等功能;
bufferevent : 更好的封装Libevent 内核,让程序可直接读写缓存,不需要实时监听 sockets来读写数据。可提供多后台运行线程,能够利用系统的快速非阻塞IO机制,例如Windows的IOCP API。
evbuffer : 该模块实现bufferevents下的缓存,提供高效的与或访问功能;
evhttp : 简单的HTTP client/server 实现;
evdns : 简单的DNS client/server 实现;
evrpc : 简单的RPC实现;

  • 组成库

编译Libevent时,默认安装以下库:
libevent_core : 所有event 和 buffer核心功能,该库包含所有的event_base, evbuffer, bufferevent和功能函数;
libevent_extra:该库定义了包含HTTP, DNS 和 RPC等 特定协议功能;
libevent:历史遗留,包含了 libevent_core 和 libevent_extra的内容,一般调用用不到该部分,未来版本可能会删除;

  • 头文件

现在所有Libevent头文件安装在文件夹event2下,分为三大类:
API headers:API头文件定义了 Libevent的公共接口,这些头文件都没有特殊后缀;
Compatibility headers:包含了过时函数的定义,除非使用老版本的Libevent,否则不要包含这些头文件;
Structure headers:很明显,自然是放定义结构体的头文件了;

  • 如何使用老版本的Libevent

前段时间因为公司项目需要,在我们的程序中对Libevent进行了升级,在版本更替的过程中,确实出现了很多接口的改变,这确实是让人很头疼的事,接下来我们就要写一下“If you have to work with an old version of Libevent” :

从Libevent 2.0开始已将API修改的更合理、容错性更好,但是有时候因为某些原因我们需要使用老版本(低于Libevent 2.0)的API,老版本的Libevent具有更少的头文件,且不在文件夹“event2”下,如图所示:
这里写图片描述

在 Libevent 2.0以后, 老版本的头文件被封装在新版头文件里。

1 设置 Libevent库

Libevent有一些整个库共享的全局设置,在调用库的任一部分之前都需要修改这些设置,否则有可能会出现不一致的状态。

  • log日志

Libevent能够打印内部错误和警告信息,默认情况下这些信息都是通过标准输出,所以我们也可以复写我们自己的log函数。接口如下:

#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3

/* Deprecated; see note at the end of this section */
#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
#define _EVENT_LOG_MSG   EVENT_LOG_MSG
#define _EVENT_LOG_WARN  EVENT_LOG_WARN
#define _EVENT_LOG_ERR   EVENT_LOG_ERR

typedef void (*event_log_cb)(int severity, const char *msg);

void event_set_log_callback(event_log_cb cb);

想要重写Libevent的log,只需要写一个上面的函数作为参数传入event_set_log_callback(),当Libevent要打印log时会将信息穿给我们自己写的函数,可以向event_set_log_callback()传入NULL使log以默认方式输出。看下面一个例子:

#include <event2/event.h>
#include <stdio.h>

static void discard_cb(int severity, const char *msg)
{
    /* This callback does nothing. */
}

static FILE *logfile = NULL;
static void write_to_file_cb(int severity, const char *msg)
{
    const char *s;
    if (!logfile)
        return;
    switch (severity) {
        case _EVENT_LOG_DEBUG: s = "debug"; break;
        case _EVENT_LOG_MSG:   s = "msg";   break;
        case _EVENT_LOG_WARN:  s = "warn";  break;
        case _EVENT_LOG_ERR:   s = "error"; break;
        default:               s = "?";     break; /* never reached */
    }
    fprintf(logfile, "[%s] %s\n", s, msg);
}

/* Turn off all logging from Libevent. */
void suppress_logging(void)
{
    event_set_log_callback(discard_cb);
}

/* Redirect all Libevent log messages to the C stdio file 'f'. */
void set_logfile(FILE *f)
{
    logfile = f;
    event_set_log_callback(write_to_file_cb);
}

注意:在调用者自己写的event_log_cb callback函数里面调用Libevent函数是不安全的。比如在回调函数里面使用bufferevents向网络socket写警告信息,很有可能遇到奇怪而又难以诊断的问题,这个局限性问题在未来版本的Libevent中可能会被解决。默认debug logs不可用,可手动开启:

#define EVENT_DBG_NONE 0
#define EVENT_DBG_ALL 0xffffffffu

void event_enable_debug_logging(ev_uint32_t which);

调用event_enable_debug_logging() 传入EVENT_DBG_NONE为默认设置,传入EVENT_DBG_ALL是开始所有log,以后的版本可能会支持更多选项。这些功能函数声明在“event2/event.h”。

  • 处理致命错误

当Libevent感知到不可恢复的内部错误时,默认执行是调用exit() 或 abort()函数退出当前运行程序,因为这些错误意味着在你写的程序中或者Libevent库中存在bug。我们还可以像下面一样重写callback函数来处理致命错误:

typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);

上面的函数同样声明在“event2/event.h”,Libevent 2.0.3-alpha以后才有。需要注意的是,在我们自定义的callback调用函数里,不能调用Libevent的任何函数。

  • 内存管理

默认情况,Libevent使用C内存管理方法从堆中分配内存,Libevent提供替换malloc, realloc, 和free的接口,这样你就可以设置自己想要使用的内存管理策略。接口如下所示,声明在“event2/event.h”, 第一次出现在Libevent 2.0.1-alpha.

void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
                             void *(*realloc_fn)(void *ptr, size_t sz),
                             void (*free_fn)(void *ptr))

例子如下:

#include <event2/event.h>
#include <sys/types.h>
#include <stdlib.h>

/* This union's purpose is to be as big as the largest of all the
 * types it contains. */
union alignment {
    size_t sz;
    void *ptr;
    double dbl;
};
/* We need to make sure that everything we return is on the right
   alignment to hold anything, including a double. */
#define ALIGNMENT sizeof(union alignment)

/* We need to do this cast-to-char* trick on our pointers to adjust
   them; doing arithmetic on a void* is not standard. */
#define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)
#define INPTR(ptr) (((char*)ptr)-ALIGNMENT)

static size_t total_allocated = 0;
static void *replacement_malloc(size_t sz)
{
    void *chunk = malloc(sz + ALIGNMENT);
    if (!chunk) return chunk;
    total_allocated += sz;
    *(size_t*)chunk = sz;
    return OUTPTR(chunk);
}
static void *replacement_realloc(void *ptr, size_t sz)
{
    size_t old_size = 0;
    if (ptr) {
        ptr = INPTR(ptr);
        old_size = *(size_t*)ptr;
    }
    ptr = realloc(ptr, sz + ALIGNMENT);
    if (!ptr)
        return NULL;
    *(size_t*)ptr = sz;
    total_allocated = total_allocated - old_size + sz;
    return OUTPTR(ptr);
}
static void replacement_free(void *ptr)
{
    ptr = INPTR(ptr);
    total_allocated -= *(size_t*)ptr;
    free(ptr);
}
void start_counting_bytes(void)
{
    event_set_mem_functions(replacement_malloc,
                            replacement_realloc,
                            replacement_free);
}
  • 加锁线程

我们知道,在多线程程序中多线程访问临界资源时需要加锁,Libevent结构体具有三种多线程工作方式:(1)本质上的单线程结构;(2)可选择加锁结构;(3)持续加锁结构;
想要实现加锁,需要告诉Libevent使用的加锁函数,如果是使用线程库或者本地windows线程代码,那就很方便了,因为库中的预定义会自动选择:

#ifdef WIN32
int evthread_use_windows_threads(void);
#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED
#endif
#ifdef _EVENT_HAVE_PTHREADS
int evthread_use_pthreads(void);
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED
#endif

上面的函数成功返回0,失败返回-1;如果想要使用别的线程库就需要做一些工作了,定义如下函数:

Locks

locking

unlocking

lock allocation

lock destruction

Conditions

condition variable creation

condition variable destruction

waiting on a condition variable

signaling/broadcasting to a condition variable

Threads

thread ID detection

然后需要调用Libevent的evthread_set_lock_callbacks和evthread_set_id_callback接口:

#define EVTHREAD_WRITE  0x04
#define EVTHREAD_READ   0x08
#define EVTHREAD_TRY    0x10

#define EVTHREAD_LOCKTYPE_RECURSIVE 1
#define EVTHREAD_LOCKTYPE_READWRITE 2

#define EVTHREAD_LOCK_API_VERSION 1

struct evthread_lock_callbacks {
       int lock_api_version;
       unsigned supported_locktypes;
       void *(*alloc)(unsigned locktype);
       void (*free)(void *lock, unsigned locktype);
       int (*lock)(unsigned mode, void *lock);
       int (*unlock)(unsigned mode, void *lock);
};

int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);

void evthread_set_id_callback(unsigned long (*id_fn)(void));

struct evthread_condition_callbacks {
        int condition_api_version;
        void *(*alloc_condition)(unsigned condtype);
        void (*free_condition)(void *cond);
        int (*signal_condition)(void *cond, int broadcast);
        int (*wait_condition)(void *cond, void *lock,
            const struct timeval *timeout);
};

int evthread_set_condition_callbacks(
        const struct evthread_condition_callbacks *);

结构体evthread_lock_callbacks定义了加锁的callback函数和他们的功能,alloc函数返回特定类型的新锁;free函数释放特定类型锁持有的所有资源;lock函数申请对特定类型加锁,unlock函数解锁,这两个函数都是返回0值代表加锁成功,非零失败;对于上面这些函数的使用例子,可以参考文件evthread_pthread.c和evthread_win32.c。

  • 查看Libevent版本

接口:

#define LIBEVENT_VERSION_NUMBER 0x02000300
#define LIBEVENT_VERSION "2.0.3-alpha"
const char *event_get_version(void);
ev_uint32_t event_get_version_number(void);

例子:Compile-time

#include <event2/event.h>

#if !defined(LIBEVENT_VERSION_NUMBER) || LIBEVENT_VERSION_NUMBER < 0x02000100
#error "This version of Libevent is not supported; Get 2.0.1-alpha or later."
#endif

int
make_sandwich(void)
{
        /* Let's suppose that Libevent 6.0.5 introduces a make-me-a
           sandwich function. */
#if LIBEVENT_VERSION_NUMBER >= 0x06000500
        evutil_make_me_a_sandwich();
        return 0;
#else
        return -1;
#endif
}

例子: Run-time

#include <event2/event.h>
#include <string.h>

int
check_for_old_version(void)
{
    const char *v = event_get_version();
    /* This is a dumb way to do it, but it is the only thing that works
       before Libevent 2.0. */
    if (!strncmp(v, "0.", 2) ||
        !strncmp(v, "1.1", 3) ||
        !strncmp(v, "1.2", 3) ||
        !strncmp(v, "1.3", 3)) {

        printf("Your version of Libevent is very old.  If you run into bugs,"
               " consider upgrading.\n");
        return -1;
    } else {
        printf("Running with Libevent version %s\n", v);
        return 0;
    }
}

int
check_version_match(void)
{
    ev_uint32_t v_compile, v_run;
    v_compile = LIBEVENT_VERSION_NUMBER;
    v_run = event_get_version_number();
    if ((v_compile & 0xffff0000) != (v_run & 0xffff0000)) {
        printf("Running with a Libevent version (%s) very different from the "
               "one we were built with (%s).\n", event_get_version(),
               LIBEVENT_VERSION);
        return -1;
    }
    return 0;
}

event_get_version()第一次出现在版本Libevent 1.0c,其他函数第一次出现在Libevent 2.0.1-alpha。

  • 释放全局结构体

尽管已经释放了Libevent分配的所有对象,还是会有一些分配的全局结构体存在,虽然程序退出时它们也会被回收,但是对于一些调试工具而言会被误导为是Libevent库的弱链接资源,调用下面的接口函数可主动释放所有的全局结构体对象:

void libevent_global_shutdown(void);

2 event_base

在使用libevent时需要分配一个或多个event_base,每个event_base持有多个event,还能够决定哪一个event处于活动状态。为了多线程访问安全,event_base可加锁,但是同一时刻只能有一个线程在运行loop。
每个event_base都有自己的“method”或后台决定哪个event是待运行状态,“method”包括以下这些:select、poll、epoll、kqueue、devpoll、evport、win32。调用者可以通过变量关闭特定后台。例如你想关闭kqueue后台就设置环境变量EVENT_NOKQUEUE等。

  • 创建默认设置的event_base

event_base_new()可以分配并返回一个默认设置的event_base,出错则返回NULL,对于大多数程序来说,默认设置生成的event_base的足够使用了。 event_base_new()函数声明在文件“event2/event.h”,在版本1.4.3第一次开始使用。

struct event_base *event_base_new(void);
  • 创建复杂的event_base

通过event_config可自定义event_base。event_config可存储关于一个event_base的偏好设置信息,可通过向函数 event_base_new_with_config()传入event_config来获取想要的event_base。

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);

通过event_config_new()生成event_config,然后调用其他函数对其设置个人需求,最后调用event_base_new_with_config()生成event_base,使用完以后通过调用 event_config_free()释放event_config。

int event_config_avoid_method(struct event_config *cfg, const char *method);

enum event_method_feature {
    EV_FEATURE_ET = 0x01,
    EV_FEATURE_O1 = 0x02,
    EV_FEATURE_FDS = 0x04,
};
int event_config_require_features(struct event_config *cfg,
                                  enum event_method_feature feature);

enum event_base_config_flag {
    EVENT_BASE_FLAG_NOLOCK = 0x01,
    EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
    EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
    EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
    EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
    EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg,
    enum event_base_config_flag flag);

调用event_config_avoid_method()函数通知Libevent 通过名字消除特定功能的后台;调用event_config_require_feature()通知Libevent不使用不具有该属性的的后台;调用event_config_set_flag()通知Libevent构造event base时设置一个或多个运行时标识。

常用特征值有以下几种:
EV_FEATURE_ET :后台方法需支持 edge-triggered IO
EV_FEATURE_O1 :要求后台方法增删单一event或者将某event激活,是一个O(1)操作。
EV_FEATURE_FDS:要求后台方法不仅支持sockets,而且可以支持任意文件描述符类型。

event_config_set_flag()函数常用选项值有以下几种:
EVENT_BASE_FLAG_NOLOCK :不为event_base分配锁,设置此选项可以节省一点时间锁定和释放的event_base ,但是也会使多线程访问时变得不安全。
EVENT_BASE_FLAG_IGNORE_ENV:当选择使用哪个后台方法时不会检查EVENT_*环境变量,若是打算使用该标识请三思而后行:它会增加使用者调试Libevent与程序之间相互作用的难度。
EVENT_BASE_FLAG_STARTUP_IOCP:只适用于windows,
EVENT_BASE_FLAG_NO_CACHE_TIME:每次超时回调才会检查,而不是event loop一直实时检查。
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST :
EVENT_BASE_FLAG_PRECISE_TIMER :

注意:因为操作系统的区别(例如windows,linux)如果调用event_config配置了Libevent在该系统下无法提供的设置,event_base_new_with_config()将返回NULL

3 Running an event loop

(待更新)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值