现在的位置: 首页 > 综合 > 正文

libevent介绍

2013年10月14日 ⁄ 综合 ⁄ 共 12509字 ⁄ 字号 评论关闭

 

libevent讲解

 什么是libevent

libevent 是一个轻量级的事件触发的网络库。它适用于windows、linux、bsd等多种平台,它是跨平台的。libevent是c语言编写的一个开源的网络库。

libevent 的设计目标是:

1、可移植性,它是跨平台的。
2、效率高。基于事件驱动(event-driven),采用非阻塞I/O,对事件异步处理;

       libevent API提供了一种当某事件的条件发生时再去调用回调函数去处理机制。事件条件的触发一般是文件描述符可读或可写,定时器时间到;回调函数一般是调用者事先编好的处理函数。这种异步机制类似于linux信号的异步处理机制。

3、可扩展性。既使活动的套接字达到成千上万,它也能很好的工作。(有关于讨论如果连接数上万会有某些瓶颈,我还没来得及深究,暂时没有能力对它评价)

4、轻量级,专注于底层网络库;

5、使用方便。用libevent编写程序的方式应该是稳定的、可移植的。

另外,libevent还有如下特点:

1、支持多种I/O多路复用技术;

几乎所有UNIX平台都支技的poll、select,Linux的epoll,以Solaris为主的/dev/pool,以BSD平台为主的kquque;libevent对这些接口进行了条件预编译封装,从而到了跨平台。
2、支持异步I/O,定时器和信号等事件,宏观上对这三种事件的处理集成在一起;
3、对注册好的事件进行了等待分类,就绪优先级调用;

当添加的是事件是I/O、信号事件时,它会分别加到这两个双链表中,如果是定时器事件,会加一个以时间作为key小根堆中,libevent 之前对定时事件的管理用的是红黑树,小根堆相对而言查找效率上高一些;调度就绪链表是分级的双向链表。

       4、支持多线程。

libevent1.4.7及以前版本已经弃用,1..3之前的版本有不少bug。libevent当前的最新稳定版是2.0.16。

libevent从1.2.* 版本开始支持轻量级的http server 开发,随后陆续还推出轻量级 DNS server、RPC server 开发。

libevent已经被广泛的应用,作为底层的网络库;目前有应用到libevent的应用有:

The usefulness of libevent API is demonstrated by the following applications:

  • Chromium – Google's open-source web browser (uses Libevent on Mac and Linux)
  • Memcached – a high-performance, distributed memory object caching system
  • Transmission – a fast, easy, and free BitTorrent client
  • NTP – the network time protocol that makes your clock right (uses Libevent in SNTP)
  • tmux – A clean, modern, BSD-licensed terminal multiplexer, similar to GNU screen
  • Tor – an anonymous Internet communication system.
  • libevhtp – A fast and flexible replacement for libevent's httpd API
  • Prosody – A Jabber/XMPP server written in Lua
  • PgBouncer – Lightweight connection pooler for PostgreSQL
  • redsocks – a simple transparent TCP -> Socks5/HTTPS proxy daemon.
  • Vomit – Voice Over Misconfigured Internet Telephones
  • Crawl – A Small and Efficient HTTP Crawler
  • Libio – an input/output abstraction library
  • Honeyd – a virtual honeynet daemon – can be used tofight Internet worms.
  • Fragroute – an IDS testing tool
  • Nylon – nested proxy server
  • Disconcert – a Distributed Computing Framework for Loosely-Coupled Workstations.
  • Trickle – a lightweight userspace bandwidth shaper.
  • watchcatd – software watchdog designed to take actions not as drastic as the usual solutions, which reset the machine.
  • ScanSSH – a fast SSH server and open proxy scanner.
  • Nttlscan – a network topology scanner for Honeyd.
  • NetChat – a combination of netcat and ppp's chat.
  • Io – a small programming language; uses libevent for network communication.
  • Systrace – a system call sandbox.
  • SpyBye – detect malware on web pages.
  • GreenSQL – an SQL database firewall.
  • dnsscan – a fast scanner for identifying open recursive dns resolvers
  • Kargo Event – a PHP extension for libevent.
  • wlmproxy – a transparent proxy server for the MSN Messenger protocol

 

Libevent的安装

1、先从官网www.libevent.org下载最新的稳定版本,我前两天下载最新的是libevent-2.0.15-stable,现在更新到libevent-2.0.16-stable了;

2、将软件包解压。如果是从windows平台通过软件连接到linux服务器的,可能要以解压的文件修改权限,通常添加一个可执行权限就可正常安装了;

3、在解压的文件夹目录下,./configure;

4、make;

5、sudo make install;

如果这几个步骤都没有出现错误,说明已经安装好,可以下面命令查看安装的库文件。

ls -al /usr/lib | grep libevent(或 ls -al /usr/local/lib | grep libevent,取决于你安装在哪个目录下面),输出一些库文件信息,说明真的安装好了。

编译时加上选项     -levent。如gcc test.c –o test –levent.

我在编写一个小程序测试的时候,发现了一个有意思的小问题。我的小程序是输出libevent的版本号,我安装的是libevent-2.0.15-stable,结果它却输出了1.4.12-stable,怎么会输出这么老掉牙的版本号?我就纳闷了,去/usr/lib/目录查看,发现里面的库文件确实是1.4.12,我再去/usr/local/lib/下面查看,发现我的libevent装在这个目录下了。原来,那个老版本是同事之前装memcache的时候装上去的,系统优先找到这个目录下面的库文件。那我怎样使用的我最近安装的新版本库呢?其实很简单,不需要卸载旧版本,只要在编译时加上选项-L/usr/local/lib/来告编译器你要使用的库文件在这个目录就可以了。如,我用gcc
test.c –o test -L/usr/local/lib/ -levent,一切OK,输出了新版本号2.0.15-stable。

顺便说一下编写程序添加libevent头文件的问题。老版本是include <event.h>,新版本是用include <event2/event.h>,至于从哪个版本开始使用include <event2/event.h>我就没有考证了,当然老版本是兼容旧版本的。新版本改进了老版本的一些接口,也增加了一些新的接口。

 

Libevent整体结构

      libevent分成下面几个组件:

1、  evutil

其功能是抽象出不同平台网络实现差异的通用性。

2、  event and event_base

这这两者libevent的核心。它提供了支持不同平台特有的,基于事件驱动的非阻塞IO的抽象接口。它可以通知你文件描述符在何时可读或可写,处理基本的定时事件以及检测系统信号,然后调用相应的回调函数进行处理。

Event是libevent的基本操作单元。每个event都有一组触发事件的条件,包括:

·         A file descriptor being ready to read from or write to.

·         A file descriptor becoming ready to read from or write to (Edge-triggered IO only).

·         A timeout expiring.

·         A signal occurring.

·         A user-triggered event.

  Events have similar lifecycles. Once you call a Libevent function to set up an event and associate it with an event base, it becomesinitialized. At this point, you can
add, which makes itpending in the base. When the event is pending, if the conditions that would trigger an event occur (e.g., its file descriptor changes state or its timeout expires), the event becomesactive, and
its (user-provided) callback function is run. If the event is configuredpersistent, it remains pending. If it is not persistent, it stops being pending when its callback runs. You can make a pending event non-pending bydeleting it,
and you can add a non-pending event to make it pending again.

         这里提到了event的几种状态,我在啰嗦一遍:

★初始化状态

    创建event,配置event的基本信息,将event与event_base邦定,完成这个过程event就被初始化了,调用event_new()就可以完成这个过程。

★未决状态

         Event初始化后,再调用event_add()将event添加到监视事件集中,event就变成的未决状态。

★激活状态

         当事件的触发条件发生时,event就变成了激活状态,然后会调用回调函数做相应的操作。

 

3、  bufferevent

为libevent基于事件的核心提供使用更方便的封装。除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时IO已经真正发生。(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞IO方式,如Windows中的IOCP。)

4、 evbuffer

在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数。

5、 evhttp

一个简单的HTTP客户端/服务器实现。

6、 evdns

一个简单的DNS客户端/服务器实现。

7、 evrpc

一个简单的RPC实现。

      libevent安装后的库文件

    安装libevent时,默认安装下列库:

v libevent_core:所有核心的事件和缓冲功能,包含了所有的event_base、evbuffer、bufferevent和工具函数。

v libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括HTTP、DNS和RPC。

v libevent:这个库因为历史原因而存在,它包含libevent_core和libevent_extra的内容。不应该使用这个库,未来版本的libevent可能去掉这个库。

某些平台上可能安装下列库:

v libevent_pthreads:添加基于pthread可移植线程库的线程和锁定实现。它独立于libevent_core,这样程序使用libevent时就不需要链接到pthread,除非是以多线程方式使用libevent。

v libevent_openssl:这个库为使用bufferevent和OpenSSL进行加密的通信提供支持。它独立于libevent_core,这样程序使用libevent时就不需要链接到OpenSSL,除非是进行加密通信。

 

      libevent的头文件

libevent公用头文件都安装在event2目录中,分为三类:

v API头文件:定义libevent公用接口。这类头文件没有特定后缀。

To browse the complete documentation of the libevent API, click on any of the following links.

event2/event.h The primary libevent header

event2/thread.h Functions for use by multithreaded programs

event2/buffer.h andevent2/bufferevent.h
Buffer management for network reading and writing

event2/util.h Utility functions for portable nonblocking
network code

event2/dns.h Asynchronous DNS resolution

event2/http.h An embedded libevent-based HTTP server

event2/rpc.h A framework for creating RPC servers and clients

 

v 兼容头文件:文件名有后缀compat,为已废弃的函数提供兼容的头部包含定义。不应该使用这类头文件,除非是在移植使用较老版本libevent的程序时。

v 结构头文件:这类头文件以相对不稳定的布局定义各种结构体。这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露。直接依赖这类头文件中的任何结构体都会破坏程序对其他版本libevent的二进制兼容性,有时候是以非常难以调试的方式出现。这类头文件具有后缀“_struct.h”。

(还存在 一些在../event2目录中的较老版本libevent的头文件,请参考下节:如果需要使用老版本libevent)

libevent 2.0.*以更合理的、不易出错的方式修正了API。如果可能,编写新程序时应该使用libevent 2.0。但是有时候可能需要使用较老的API,例如在升级已存的应用时,或者支持因为某些原因不能安装2.0或者更新版本libevent的环境时。

较老版本的libevent头文件较少,也不安装在event2目录中。在2.0以及以后版本的libevent中,老的头文件仍然会作为新头文件的封装而存在。

其他关于使用较老版本的提示:

v 1.4版之前只有一个库libevent,它包含现在分散到libevent_core和libevent_extra中的所有功能。

v 2.0版之前不支持锁定:只有确定不同时在多个线程中使用同一个结构体时,libevent才是线程安全的。

下面的节还将讨论特定代码区域可能遇到的已经废弃的API。

libevent源代码文件的说明

         ps:此节引用于“libevent源码深度剖析”

 

Libevent的源代码虽然都在一层文件夹下面,但是其代码分类还是相当清晰的,主要可分为头文件、内部使用的头文件、辅助功能函数、日志、libevent框架、对系统I/O多路复用机制的封装、信号管理、定时事件管理、缓冲区管理、基本数据结构和基于libevent的两个实用库等几个部分。源代码中的test和sample文件夹下面就是一些测试的例子了,可以拿来学习试手。
1)头文件
主要就是event.h:事件宏定义、接口函数声明,主要结构体event的声明;
2)内部头文件
xxx-internal.h:内部数据结构和函数,对外不可见,以达到信息隐藏的目的;
3)libevent框架
event.c:event整体框架的代码实现;
4)对系统I/O多路复用机制的封装
epoll.c:对epoll的封装;

select.c:对select的封装;
devpoll.c:对dev/poll的封装;
kqueue.c:对kqueue的封装;
5)定时事件管理
min-heap.h:其实就是一个以时间作为key的小根堆结构;
6)信号管理
signal.c:对信号事件的处理;
7)辅助功能函数
evutil.h 和evutil.c:一些辅助功能函数,包括创建socket pair和一些时间操作函数:加、减和比较等。
8)日志
log.h和log.c:log日志函数
9)缓冲区管理
evbuffer.c和buffer.c:libevent对缓冲区的封装;
10)基本数据结构
compat/sys下的两个源文件:queue.h是libevent基本数据结构的实现,包括链表,双向链表,队列等;_libevent_time.h:一些用于时间操作的结构体定义、函数和宏定义;
11)实用网络库
http和evdns:是基于libevent实现的http服务器和异步dns查询库;

 

libevent事件处理流程

ps:此节引用于“libevent源码深度剖析”

 

当应用程序向libevent 注册一个事件后,libevent 内部是怎么样进行处理的呢?下面的

图就给出了这一基本流程。

1 )  首先应用程序准备并初始化event,设置好事件类型和回调函数;这对应于前面第步骤

2 和3 ;

2 )  向libevent 添加该事件 event。对于定时事件,libevent 使用一个小根堆管理,key 为超

时时间;对于 Signal 和I/O 事件,libevent 将其放入到等待链表(wait list)中,这是一

个双向链表结构;

3 )  程序调用event_base_dispatch() 系列函数进入无限循环,等待事件,以select() 函数为例;

每次循环前libevent 会检查定时事件的最小超时时间 tv ,根据 tv 设置select() 的最大等

待时间,以便于后面及时处理超时事件;

当select() 返回后,首先检查超时事件,然后检查I/O 事件;

  11

Libevent 将所有的就绪事件,放入到激活链表中;

然后对激活链表中的事件,调用事件的回调函数执行事件处理;

libevent对event 的管理

从event 结构体中的 3 个链表节点指针和一个堆索引出发,大体上也能窥出 libevent 对

event的管理方法了,可以参见下面的示意图。每次当有事件event转变为就绪状态时,libevent 就会把它移入到active event list[priority]中,其中priority是event的优先级;

接着libevent 会根据自己的调度策略选择就绪事件,调用其cb_callback()函数执行事件

处理;并根据就绪的句柄和事件类型填充cb_callback 函数的参数。

 

libevent的一般编程思路

创建event_base实例

(1) struct event_base *event_base_new(void);方法,创建默认的event_base

        注意:老版本是用struct
event_base *event_init(void),
这个函数在新版本中已经弃用,因为它会初始化当前event_base,在使用多线程的时候是不安全的。

(2) event_base_new_with_config(),创建带配置的event_base.

调用接口:

struct event_config *event_config_new(void);

struct event_base *event_base_new_with_config(conststruct
event_config *cfg);

void event_config_free(struct
event_config *cfg);

        

event_baselibevent的中心,每个应用必须有一个,它监视事件未决和活动的事件,并且通知应用程序活动的事件,好让应用程序在事件发生时调用回调函数对它们进行处理。

 

 

 

 

创建event对象

 

对于一个我们想监视的文件描述符,首先得建一个event结构体,然后将fdeventfd成员邦定在一起。建一个event结构体方法可以是:

1event_new()方法

接口:

#define EV_TIMEOUT     0x01

#define EV_READ        0x02

#define EV_WRITE       0x04

#define EV_SIGNAL      0x08

#define EV_PERSIST     0x10

#define EV_ET          0x20  
//
edge-triggered

 

typedefvoid
(*event_callback_fn)(evutil_socket_t,
short,void
*);

 

struct event *event_new(struct
event_base *base, evutil_socket_t fd,

   short what, event_callback_fn cb,

   void *arg);

 

void event_free(struct
event *event);

 

The event_new() function tries to allocate and construct a new event for use withbase.
The what argument is a set of the flags listed above. (Their semantics are described below.) Iffd is nonnegative, it is the file that we’ll observe for read or write events.
When the event is active, Libevent will invoke the providedcb function, passing it as arguments: the file descriptorfd,
a bitfield ofall the events that triggered, and the value that was passed in forarg when the function was constructed.

All new events are initialized and non-pending. To make an event pending, call event_add() (documented below).

To deallocate an event, call event_free().It is safe to call event_free() on an event that is pending or active: doing so makes the event non-pending and inactive before
deallocating it.

函数说明:

struct event *event_new(struct
event_base *base, evutil_socket_t fd,

   short what, event_callback_fn cb,

   void
*arg)

Parameters:

 

base 

the event base to which the event should be attached.

 

fd 

the file descriptor or signal to be monitored, or -1.

 

what 

desired events to monitor: bitfield of EV_READ, EV_WRITE, EV_SIGNAL, EV_PERSIST, EV_ET.

 

cb 

callback function to be invoked when the event occurs

 

arg 

an argument to be passed to the callback function

     

Returns:

a newly allocated struct event that must later be freed withevent_free().

        Or,   
on an internal error, or invalid arguments, event_new() will return NULL

注:当参数fd-1时,事件只能被定时器触发或用event_active()手动活激事件。

The event flags

EV_TIMEOUT

This flag indicates an event that becomes active after a timeout elapses.

The EV_TIMEOUT flag is ignored when constructing an event: you
can either set a timeout when you add the event, or not.  It is
set in the 'what' argument to the callback function when a timeout
has occurred.

EV_READ

This flag indicates an event that becomes active when the provided file descriptor is ready for reading.

EV_WRITE

This flag indicates an event that becomes active when the provided file descriptor is ready for writing.

EV_SIGNAL

Used to implement signal detection. See "Constructing signal events" below.

EV_PERSIST

Indicates that the event ispersistent. See "About Event Persistence" below.

EV_ET

Indicates that the event should be edge-triggered, if the underlying event_base backend supports edge-triggered events. This affects the semantics of
EV_READ and EV_WRITE.

Since Libevent 2.0.1-alpha, any number of events may be pending for the same conditions at the same time. For example, you may have two events that will become active if a given
fd becomes ready to read. The order in which their callbacks are run is undefined.

These flags are defined in <event2/event.h>. All have existed since before Libevent 1.0, except for EV_ET, which was introduced in Libevent 2.0.1-alpha.

About Event Persistence

By default, whenever a pending event becomes active (because its fd is ready to read or write, or because its timeout expires), it becomes non-pending right before its callback
is executed. Thus, if you want to make the event pending again, you can call event_add() on it again from inside the callback function.

If the EV_PERSIST flag is set on an event, however, the event ispersistent. This means that event remains pending even when its callback is activated. If you want to
make it non-pending from within its callback, you can call event_del() on it.

The timeout on a persistent event resets whenever the event’s callback runs. Thus, if you have an event with flags EV_READ|EV_PERSIST and a timeout of five seconds, the event
will become active:

·        Whenever
the socket is ready for reading.

·        Whenever
five seconds have passed since the event last became active.

 

 

See also:

event_free(),event_add(),event_del(),event_assign()

 

(1)    也可以先申明一个结构体,然后调用event_assign()来初始化结构体成员。

创建好event后,用event_add()添加事件通知。这个结构最好分配在堆上,因为event_base要一直对它监视,直到你把它撤销。

 

int event_assign

(

structevent

,

   

structevent_base

,

   

evutil_socket_t 

,

   

short 

,

   

event_callback_fn 

,

   

void * 

 

 

 

抱歉!评论已关闭.