i007.cc

i007.cc

优先队列-降维打击

《libevent深入浅出》 学习笔记

Libevent特点:

  • 事件驱动,高性能
  • 轻量级,专注于网络
  • 跨平台,支持Windows,linux,Mac os等
  • 支持多种I/O复用技术,epoll,poll,dev/poll,select 和kqueue等
  • 支持I/O,定时器和信号等事件

创建event_base

创建默认的event_base

/*
@brief create default event_base
@return struct event_base*: a pointer to struct event_base or failure return NULL
*/
struct event_base *event_base_new(void);
//释放event_base
void event_base_free(struct event_base *base);

创建复杂的event_base

如果需要对event_base有更多的控制,需要使用event_config

/*
@brief create a default event_config
@return event_config* :failure to return null
*/
struct event_config *event_config_new(void);
//create a event_base with event_config
struct event_base_new_with_config(const struct event_config*cfg);
//free event_config
void event_config_free(struct event_config* cfg);

要配置复杂的event_base,先调用event_config_new()分配event_config,然后对event_config调用其他函数,设置所需要的event_base特征。最后调用event_base_new_with_config()获取新的event_base,完成之后使用event_config_free()释放event_config.

//通过名字 设置libevent不使用特定的可用后端
int event_config_avoid_method(struct event_config *cfg,const char* method);
//设置libevent 不使用不能提供指定特征的后端
enum event_method_feature{
  EV_FEATURE_ET = 0x01, //要求支持边沿触发的后端
  EV_FEATURE_O1 = 0x02, // 要求添加,删除单个事件,或者确定哪个事件激活的操作是O(1)复杂度的后端
  EV_FEATURE_FDS = 0x04 // 要求支持任意文件描述符,而不仅仅是套接字的后端
};
//设置成功返回0,失败返回-1
int event_config_require_features(struct event_config *cfg,
                                  enum event_method_feature feature);

//设置创建event_base时设置一个活着多个运行时的标志
enum event_base_config_flag{
  EVENT_BASE_FLAG_NOLOCK = 0x01, //不要为event_base分配锁,容易造成多线程不安全
  EVENT_BASE_FLAG_IGNORE_ENV = 0x02, // 使用的后端,不要检测EVENT*环境变量,使用这个标志和更难调试
  EVENT_BASE_FLAG_STARTUO_IOCP = 0x04, //仅仅用于WINDOWS,让libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用
  EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08, // 在每次超时回调后进行检测当前时间
  EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10, //如果果决定使 用 epoll 后端,可以安全地使用更快的基于 changelist 的后端。但是如果传递任何使用 dup()或者其变体克隆的 fd 给 libevent,epoll-changelist 后端会触发一个内核 bug,导致不正确的结果, EVENT_EPOLL_USE_CHANGELIST环境变量用于打开epoll-changelist
  EVENT_BASE_FLAG_PRECISE_TIMER = 0x20, //是否使用更精准的时间
};
//设置成功返回0,失败返回-1
int event_config_set_flag(struct event_config *cfg,
                         enum event_base_config_flag flag);

检查event_base

//返回一个指针,指向libevent支持的方法的名字数字,数组的最后一个元素是NULL
const char **event_get_supported_methods(void);
//获取当前event_base正在使用的方法
const char* eevnt_base_get_method(const struct event_base* base);
//返回event_base支持的特征的比特掩码
enum event_method_feature event_base_get_features(const struct event_base* base);

设置event_base优先级

libevent支持为多个事件设置优先级,event_base默认只支持单个优先级,可以调用event_base_priority_init()设置event_base的优先级,必须在任何事件激活之前调用,最好在创建event_base之后调用

//成功返回0 失败返回-1,常量EVENT_MAX_PRIORITIES表示n_priorities的上限。每个新事件的优先级将从0(最高)到n_priorities-1最低
int event_base_priority_init(struct event_base* base,int n_priorities);

结合fork

如果在新进程中需要继续使用event_base,就需要进行重新初始化。

//成功返回0 失败返回-1
int event_reinit(struct event_base *base);

创建事件event

使用event_new()接口创建事件

#define EV_TIMOUT   0x01 //表示事件超时后悔变成激活的
#define EV_READ     0x02 //表示指定的文件描述符读事件就绪
#define EV_WRITE    0x04 //表示指定的文件描述符 写事件就绪
#define EV_SIGNAL   0x08 // 用于实现信号检测
#define EV_PERSISIT 0x10 //表示事件是持久的
#define EV_ET       0x20 //如果支持边沿触发,那么事件是边沿触发的会影响EV_READ和EV_WRITE

//回调函数
typedef void (*event_callback_fn)(evutil_socket_t,short,void*);


//分配和构造一个用于base的新的事件,what参数是上述标志的集合,事件被激活时,libevent调用cb函数,fd是文件描述符。what表示触发的事件的位字段,以及构造事件时的arg参数。发生错误或者传入无效参数时,event_new()将返回NULL
struct event* event_new(struct event_base* base,evutil_socket_t fd,
                        short what,event_callback_fn cb,
                        void *args);
//释放event 事件。对于未决的获知激活状态的事件调用event_free()是安全的,在释放事件之前,函数会将事件成为非激活的和非未决的。
void event_free(struct event* event);

事件的持久性,如果想在回调中让事件成为非未决的,可以对其调用event_del().每次执行事件回调的时候,持久事件的超时值会被复位。

信号事件,libevent可以检测信号事件,处理提供一个信号编号代替文件描述符之外,各个参数与event_new()相同

#define evsignal_new(base,signum,cb,arg) \
        event_new(base,signum,EV_SIGNAL|EV_PERSIST,cb,arg)

libevent有以下的宏用来处理信号事件

#define evsignale_add(ev,tv) \
        event_add((ev),(tv))
#define evsignal_del(ev) \
        event_del(ev)
#define evsignal_pending(ev,what,tv_out) \
        event_pending((ev),(what),(tv_out))

设置未决事件:

添加事件,如果添加成功返回0,失败返回-1,如果tv为NULL,添加的事件不会超时,否则超时时间未tv中设置的值

int event_add(struct event* ev,const struct timeval *tv);

设置非未决事件

int event_del(struct event* ev);

对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。成功时函数返回 0,失败时返回-1。回调在执行前删除事件,则不会执行。

设置事件的优先级:

在初始化事件之后,但是在添加到event_base之前,可以为其设置优先级。

int event_priority_set(struct event *event,int priority);

事件的优先级是一个在0和event_base的优先级减去1之间的数值,成功时函数返回0,失败返回-1.libevent会执行高优先级的事件,在没有高优先级事件激活的时候,低优先级事件才会运行。

检查事件:

/**
  Checks if a specific event is pending or scheduled.
  @param ev an event struct previously passed to event_add()
  @param events the requested event type; any of EV_TIMEOUT|EV_READ|
         EV_WRITE|EV_SIGNAL
  @param tv if this field is not NULL, and the event has a timeout,
         this field is set to hold the time at which the timeout will
     expire.
  @return true if the event is pending on any of the events in 'what', (that
  is to say, it has been added), or 0 if the event is not added.
 */
int event_pending(const struct event* ev,short what,struct timeval* tv_out);

#define event_get_signal(ev) ((int)event_get_fd(ev))
/**
   Get the socket or signal assigned to an event, or -1 if the event has
   no socket.
evutil_socket_t event_get_fd(const struct event* ev);
/**
   Get the event_base associated with an event.
*/
struct event_base *event_get_base(const struct event* ev);
/**
   Return the events (EV_READ, EV_WRITE, etc) assigned to an event.
*/
short event_get_events(const struct event *ev);
/**
   Return the callback assigned to an event.
*/
event_callback_fn event_get_callback(const struct event* ev);
/**
   Return the callback argument assigned to an event.
*/
void *event_get_callback_arg(const struct event* ev);

int event_get_priority(const struct event* ev);
/**
   Extract _all_ of arguments given to construct a given event.  The
   event_base is copied into *base_out, the fd is copied into *fd_out, and so
   on.
   If any of the "_out" arguments is NULL, it will be ignored.
 */
void event_get_assignment(const struct event* event,
                         struct event_base **base_out,
                         evutil_socket_t *fd_out,
                         short *events_out,
                         event_callback_fn* callback_out,
                         void **arg_out);

一次触发事件

如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的,则可以使用event_base_once(),不支持EV_SIGNAL和EV_PERSIST。安排的事件将以默认的优先级加入到event_base并执行。执行完成后,会释放event结构,成功时返回0,失败时返回-1。

int event_base_once(struct event_base *base,
                    evutil_socket_t fd,
                    short what
                   event_callback_fn cb,
                   void *arg,const struct timeval* tv);

手动激活事件:

在某些情况下,需要在事件的条件没有触发的时候让事件激活。

void event_active(struct event* ev,int what,short ncalls);

事件循环

一旦有了一个已经注册了的某些事件的event_base,就需要让libevent等待事件并且通知事件的发生。

#define EVLOOP_ONCE             0x01
#define EVLOOP_NOBLOCK          0x02
#define EVLOOP_NO_EXIT_ON_EMPTY  0x04

int event_base_loop(struct event_base *base,int flags);

默认情况下,event_base_loop()函数运行event_base直到其中没有已经注册的事件为止。

flag: EVLOOP_ONCE循环将等待某些事件成为激活的 ,执行激活的事件直到没有更多的事件可以执行,然会返回。如果设置了 EVLOOP_NONBLOCK,循环不会等待事件被触发: 循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调。

也可以使用

int event_base_dispatch(struct event_base* base);

event_base_dispatch()等同于没有设置标志的event_base_loop()。将一直运行直到没有已经注册的事件或者调用下列函数

//event_base_loopexit()让 event_base 在给定时间之后停止循环。如果 tv 参数为 NULL, event_base 会立即停止循环,没有延时。
int event_base_loopexit(struct event_base *base,
                        struct timeval *tv);
//event_base_loopbreak ()让 event_base 立即退出循环。
int event_base_loopbreak(struct event_base *base);

转储event_base的状态:

调用event_base_dump_events()可以将event_base的信息输出到指定的文件中

void event_base_dump_events(struct event_base* base,FILE *f);

数据缓冲Bufferevent

bufferevent由一个底层的传输端口,一个读取缓冲区和一个写入缓冲区组成。bufferevent在读取或者写入足够量的数据之后调用用户提供的回调。每个bufferevent都有一个输入缓冲区和输出缓冲区,类型都是evbuff

每个bufferevent有两个数据相关的回调,一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调。输出缓冲区中足够量的数据被清空到底层传输端口后调用写入回调函数。每个bufferevent有四个水位:

  • 读取低水位 : 取操作使得输入缓冲区的数据量在此级别或者更高时 ,读取回调将被调用。默认值为 0,所以每个读取操作都会导致读取回调被调用。
  • 读取高水位:输入缓冲区中的数据量达到此级别后, bufferevent 将停止读取,直到输入缓冲区中足够量的数据被抽取 ,使得数据量低于此级别
  • 写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时 ,写入回调将被调用
  • 写入高水位:bufferevent 没有直接使用这个水位

默认情况下,bufferevent 的回调在相应的条件发生时立即被执行,在依赖关系复杂的情况下 ,这种立即调用会制造麻烦 。要解决这样的麻烦,可以请求bufferevent延迟其回调。

创建bufferevent使用的标志:

  • BEV_OPT_CLOSE_ON_FREE: 释放bufferevent时关闭底层传输端口
  • BEV_OPT_THREADSAFE: 自动未bufferevent分配锁,多线程安全
  • BEV_OPT_DEFER_CALLBACKS:设置bufferevent延迟回调
  • BEV_OPT_UNLOCK_CALLBACKS:设置bufferevent在执行回调的时候不进行锁定。

创建基于套接字的bufferevent

struct bufferevent *bufferevent_socket_new(
    struct event_base *base,
    evutil_socket_t fd,
    enum bufferevent_options options);

options是创建bufferevent使用的标志的掩码,fd是套接字文件描述符,base是event_base,成功时函数返回一个bufferevent,失败返回NULL。

启动链接:

int bufferevent_socket_connect(struct bufferevent *bev,
                            struct sockaddr* address,
                            int addrlen);

连接完成之前可以向输出缓冲区添加数据。如果连接成功启动,函数返回0;如果发生错误则返回-1。

释放bufferevent

void bufferevent_free(struct bufferevent *bev);

释放bufferevent,bufferevent内部具有引用计数。如果释放还有未处理的延迟回调,则在完成之前bufferevent不会被删除。

操作回调:

typedef void (*bufferevent_data_cb)(struct bufferevent *bev,
                                    void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev,
                                    short events,
                                    void *ctx);
//设置一个或者多个回调函数,分别表示在已经读取足够的数据,已经写入足够的数据或者发生错误时调用,禁用回调,参数需要设置为NULL
void bufferevent_setcb(struct bufferevent *bufev,
                       bufferevent_data_cb readcb,
                       bufferevent_data_cb writecb,
                      bufferevent_event_cb eventcb,void* cbargs);
//获取bufferevent 的回调函数
void bufferevent_getcb(struct bufferevent *bufev,
                      bufferevent_data_cb *readcb_ptr,
                      bufferevent_data_cb *writecb_ptr,
                      bufferevent_event_cb,*event_ptr,
                      void **cbarg_ptr)

启用和禁用事件:

void bufferevent_enable(struct bufferevent *bufev,short events);
void bufferevent_disable(struct bufferevent *bufev,short events);
short bufferevent_get_enabled(struct bufferevent *bufev);

启用或者禁用bufferevent上的EV_READ,EV_WRITE或者EV_READ|EV_WRITE事件。没有启用读取或者写入事件时bufferevent将不会进行数据的读取或者写入,使用bufferevent_get_enabled()获取bufferevent上当前启用的数据。

设置水位:

void bufferevent_setwatermark(struct bufferevent *bufev,
                             short events,
                             size_t lowmark,
                             size_t highmark);

设置单个bufferevent的读取水位,写入水位或者同时调整二者。对于高水位,0表示无限。

通过buffer得到evbuffer

struct evbuffer *bufferevent_get_input(struct bufferevent* bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent* bufev);

分别返回输入缓冲区和输出缓冲区。

向bufferevent的输出缓冲区添加数据

int bufferevent_write(struct bufferevent *bufev,
                      const void *data,
                     size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev,
                             struct evbuffer *buf)

bufferevent_write将内存中从data处开始的size字节数据添加到输出缓冲区的末尾,buffereevent_write_buffer()移除buf的所有内容,将其放置到输出缓冲区的末尾。函数执行成功返回0,发生错误时返回-1。

从bufferevent读取数据

size_t bufferevent_read(struct bufferevent *bufev,
                        void* data,size_t size);
int bufferevent_read_buffer(struct bufferevent *bufev,
                           struct evbuffer *buf);

读取bufferevent中的上输入缓冲区内容。bufferevent_read()至多读取size字节的数据到内存data中,函数执行成功返回读取的字节数;而bufferevent_read_buffer()则读取所有输入缓冲区的数据,函数执行成功返回0,失败返回-1.

bufferevent的清空

int bufferevent_flush(struct bufferevent *bufev,
                     short iotype,
                     enum bufferevent_flush_mode state);

iotype 可以是EV_READ,EV_WRITE或者EV_READ | EV_WRITE;

state可以是BEV_NORMAL,BEV_FLUSH,BEV_FINISHED

数据evbuffer

libevent的evbuffer实现了为向后面添加数据和从前面移除数据而优化的字节对了,evbuffer用于处理缓冲网络IO的”缓冲”部分。

创建和释放

struct evbuffer *evbuffer_new(void);
void evbuffer_free(struct evbuffer *buf);

evbuffer_new()分配和返回一个新的evbuffer,evbuffer_free()释放evbuffer和其内容。

线程安全的设置

int evbuffer_enable_locking(struct evbuffer* buf,void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer* buf);

默认情况下evbuffer是线程不安全的。如果在evbuffer_enable_lock()中的lock为NULL,那么就会evthread_set_lock_creation_callback 提供的锁创建函数创建一个锁,负责libevent将使用lock锁。evbuffer_lock()和 evbuffer_unlock()函数分别请求和释放 evbuffer 上的锁。

发布于 08-04

发表回复