2022-08-01 libevent

Libevent的使用

ps:该笔记大部分内容来自刘丹冰Aceld · 语雀 (yuque.com),侵权删

2.event_base

每个 event_base 结构 体持有一个事件集合,可以检测以确定哪个事件是激活的.

①创建event_base

//分配并且返回一个新的具有默认设置的 event_base
struct event_base *event_base_new(void);

//创建复杂的event_base
struct event_base *event_base_new_with_config(const struct event_config *cfg);
struct event_config *event_config_new(void);
void event_config_free(struct event_config *cfg);

一个事件 struct event (io事件,定时器事件,信号事件)。

②释放event_base

使用event_base之后,需要手动释放

void event_base_free(struct event_base *base);

③设置优先级

必须在任何事件激活之前调用这个函数。

int event_base_priority_init(struct event_base *base, int n_priorities);

④event_base与fork

fork后需要重新初始化event_base

int event_reinit(struct event_base *base);

⑤event_loop

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

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

int event_base_loop(struct event_base *base, int flags);
//不带flag的event_base_loop
int event_base_dispatch(struct event_base *base);

⑥停止event_loop

event_base_loopexit()让 event_base 在给定时间之后停止循环。如果 tv 参数为 NULL, event_base 会立即停止循环,没有延时。如果 event_base 当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出。

event_base_loopbreak ()让 event_base 立即退出循环。它与 event_base_loopexit (base,NULL)的不同在于,如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出。

int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);


//判断事件循环是如何退出的
int event_base_got_exit(struct event_base *base);
int event_base_got_break(struct event_base *base);

⑦转储event_base的状态C++

调用 event_base_dump_events()可以将event_base事件状态列表输出到指定的文件中。

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

3.event

①创建事件

所有新创建的事件都处于已初始化和非未决状态 ,需要调用 event_add()可以使其成为未决的。

#define EV_TIMEOUT      0x01
#define EV_READ         0x02
#define EV_WRITE        0x04
#define EV_SIGNAL       0x08
#define EV_PERSIST      0x10
#define EV_ET           0x20

typedef void (*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);

②事件标志

EV_TIMEOUT//这个标志表示某超时时间流逝后事件成为激活的。
EV_READ     //表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的。
EV_WRITE    //表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的。
EV_SIGNAL   //用于实现信号检测
EV_PERSIST //表示事件是“持久的,对其调用 event_del ()使事件成为非未决的。
EV_ET       //表示如果底层的 event_base 后端支持边沿触发事件,则事件应该是边沿触发的

③信号事件

//创建信号事件
#define evsignal_new(base, signum, cb, arg) \
    event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
//添加信号事件,使其成为未决
#define evsignal_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))

④事件的未决和非未决

构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。需要使用event_add()将事件添加到 event_base。

//设置未决事件
int event_add(struct event *ev, const struct timeval *tv);
//设置非未决事件
int event_del(struct event *ev);

⑤设置事件的优先级

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

事件的优先级是一个在 0和 event_base 的优先级减去1之间的数值。

如果不为事件设置优先级,则默认的优先级将会是 event_base 的优先级数目除以2。

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

⑥检查事件状态

event_pending()函数确定给定的事件是否是未决的或者激活的。

int event_pending(const struct event *ev, short what, struct timeval *tv_out);

//event_get_fd()和 event_get_signal()返回为事件配置的文件描述符或者信号值。
#define event_get_signal(ev) /* ... */
evutil_socket_t event_get_fd(const struct event *ev);

//event_get_base()返回为事件配置的 
struct event_base *event_get_base(const struct event *ev);

//event_base。event_get_events()返回事件的标志(EV_READ、EV_WRITE 等)。
short event_get_events(const struct event *ev);

//event_get_callback()和 event_get_callback_arg() 返回事件的回调函数及其参数指针。
event_callback_fn event_get_callback(const struct event *ev);
void *event_get_callback_arg(const struct event *ev);

//返回事件的优先级
int event_get_priority(const struct event *ev);

//复制所有为事件分配的字段到提供的指针中
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()。

int event_base_once(struct event_base *, evutil_socket_t, short,
  void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);

⑧手动激活事件

需要在事件的条件没有触发的时候让事件成为激活的。

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

4.Bufferevent

每个 bufferevent 都有一个输入缓冲区和一个输出缓冲区 ,它们的类型都是“struct evbuffer”。

①创建基于套接字的bufferevent

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

//bufferevent_options有如下选项
●  BEV_OPT_CLOSE_ON_FREE :释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等。 
●  BEV_OPT_THREADSAFE :自动为 bufferevent 分配锁,这样就可以安全地在多个线程中使用 bufferevent。 
●  BEV_OPT_DEFER_CALLBACKS :设置这个标志时, bufferevent 延迟所有回调。
●  BEV_OPT_UNLOCK_CALLBACKS :默认情况下,如果设置 bufferevent 为线程安全 的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。 

②在bufferevent上启动链接

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

③释放bufferevent操作

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

如果设置了 BEV_OPT_CLOSE_ON_FREE 标志,并且 bufferevent 有一个套接字或者底层 bufferevent 作为其传输端口,则释放 bufferevent 将关闭这个传输端口。

void bufferevent_free(struct bufferevent *bev);

④bufferevent回调

typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev,
    short events, void *ctx);

void bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg);

void bufferevent_getcb(struct bufferevent *bufev,
    bufferevent_data_cb *readcb_ptr,
    bufferevent_data_cb *writecb_ptr,
    bufferevent_event_cb *eventcb_ptr,
    void **cbarg_ptr);

//可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。
void bufferevent_enable(struct bufferevent *bufev, short events);
void bufferevent_disable(struct bufferevent *bufev, short events);

//可以调用 bufferevent_get_enabled()确定 bufferevent 上当前启用的事件。
short bufferevent_get_enabled(struct bufferevent *bufev);

⑤操作bufferevent中的数据

通过bufferevent得到evbuffer.

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

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

添加到缓冲区的数据会自动发送到对端

//从 data 处开 始的 size 字节数据添加到输出缓冲区的末尾
int bufferevent_write(struct bufferevent *bufev,
    const void *data, size_t size);
//移除 buf 的所有内 容,将其放置到输出缓冲区的末尾
int bufferevent_write_buffer(struct bufferevent *bufev,
    struct evbuffer *buf);

⑦从bufferevent的输入缓冲区读取移除数据

//移除size大小的数据保存到data处,data需要保证足够的容量
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

//抽空输入缓冲区的所有内容,将其放置到 buf 中
int bufferevent_read_buffer(struct bufferevent *bufev,
    struct evbuffer *buf);

⑧bufferevent的清空操作

失败时 bufferevent_flush()返回-1,如果没有数据被清空则返回 0,有数据被清空则返回 1。

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

5.evBuffer

①创建和释放evbuffer

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

②.evbuffer与线程安全

//使能锁
int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer *buf);

③.检查evbuffer

size_t evbuffer_get_length(const struct evbuffer *buf);
//这个函数返回 evbuffer 存储的字节数,它在2.0.1-alpha 版本中引入。

④向evbuffer添加数据

int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
//这个函数添加 data 处的 datalen 字节到 buf 的末尾,
//成功时返回0,失败时返回-1。

int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);
//这些函数添加格式化的数据到 buf 末尾。
//格式参数和其他参数的处理分别与 C 库函数 printf 和 vprintf 相同。函数返回添加的字节数。

int evbuffer_expand(struct evbuffer *buf, size_t datlen);
//这个函数修改缓冲区的最后一块,或者添加一个新的块,
//使得缓冲区足以容纳 datlen 字节, 而不需要更多的内存分配。

⑤.evbuffer数据移动

//evbuffer_add_buffer()将 src 中的所有数据移动到 dst 末尾,成功时返回0,失败时返回-1。
int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);

//evbuffer_remove_buffer()函数从 src 中移动 datlen 字节到 dst 末尾,尽量少进行复制。如果字节数小于 datlen,所有字节被移动。函数返回移动的字节数。
int evbuffer_remove_buffer(struct evbuffer *src, 
                    struct evbuffer *dst,
                    size_t datlen);

6.evconnlistener连接监听器

①.创建和释放evconnlistener

新连接到达时,监听 器调用给出的回调函数

可识别的标志:

  • LEV_OPT_LEAVE_SOCKETS_BLOCKING 会设置为阻塞的
  • LEV_OPT_CLOSE_ON_FREE 放连接监听器会关闭底层套接字
  • LEV_OPT_CLOSE_ON_EXEC 底层套接字设置 close-on-exec 标志
  • LEV_OPT_REUSEABLE 套接字是可重用的
  • LEV_OPT_THREADSAFE 为监听器分配锁,这样就可以在多个线程中安全地使用
//假定已经将套接字绑定到要监听的端口,然后通过 fd 传入这个套接字。
struct evconnlistener *
evconnlistener_new(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    evutil_socket_t fd);

//分配和绑定套接字,可以调用 evconnlistener_new_bind() ,
struct evconnlistener *
evconnlistener_new_bind(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    const struct sockaddr *sa, int socklen);

//释放连接监听器
void evconnlistener_free(struct evconnlistener *lev);

②.链接监听器回调

调整 evconnlistener 的回调函数,使用evconnlistener_set_cb。

typedef void (*evconnlistener_cb)(struct evconnlistener *listener,
    evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);

void evconnlistener_set_cb(struct evconnlistener *lev,
    evconnlistener_cb cb, void *arg);

③.启用和禁用 evconnlistener

int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);

④.检测 evconnlistener

evutil_socket_t evconnlistener_get_fd(struct evconnlistener *lev);
struct event_base *evconnlistener_get_base(struct evconnlistener *lev);

⑤.侦测错误

typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);
void evconnlistener_set_error_cb(struct evconnlistener *lev,
    evconnlistener_errorcb errorcb);

7.other

①.日志消息回调设置

#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);

②.致命错误回调设置

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

③. 内存管理回调设置

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

④锁和线程的设置

#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

example

#include <stdio.h>
#include <stdlib.h>
#include <event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <event2/listener.h>

//读事件回调
void read_cb(struct bufferevent*bev,void *arg)
{
    int fd = *(int*)arg;
    char buf[128] = 0;
    int ret = bufferevent_read(bev, buf, sizeof(buf));
    if(ret < 0)
    {
        printf("read error\n");
    }
    else
    {
        printf("recv from %d:%s",fd,buf);
    }
}

//其他事件回调
void event_cb(struct bufferevent*bev,short what,void *arg)
{
    if(what &BEV_EVNET_EOF)
    {
        printf("客户端下线\n");
        bufferevent_free(bev);      //释放buffereven对象
    }
    else
    {
        printf("未知错误");
    }
}

void listener_cb(struct evconnlistener*listener, evutil_socket_t fd,struct sockaddr*addr,int socklen, void *arg)
{
    printf("接受%d的连接\n",fd);
    struct event_base *base = arg; //需要从主函数传递过来
    
    //针对已经存在的socket创建bufferevent对象
    //参数如下:返回buffer对象
    struct bufferevent*bev = bufferevent_socket_new(base,   //事件集合
                           fd,      //文件描述符
                           BEV_OPT_CLOSE_ON_FREE //释放bufferevent对象则断开连接
                          );
    
    //给bufferevnet设置回调函数
    bufferevent_setcb(bev,          //buffereven对象
                      read_cb,      //读回调
                      NULL,         //写回调
                      event_cb,     //其他回调
                      (void*)fd;            //参数
    
    //使能事件类型
    bufferevent_enable(bev,     //buffereven对象
                       EV_READ      //使能读
                      )
    
}


int main()
{
    //创建一个事件集合
    struct event_base *base = event_base_new();
    if(NULL==base)
    {
        exit(1);
    }
    //创建监听对象
    struct sockaddr_in server_addr;
    memset(&server_addr, 0 ,sizeof(server_addr));
    server_addr.sin_port = 8000;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    //一步完成:创建socket 绑定socket,监听,接受链接四部
    //参数如下:
    struct evconnlistener *listener = evconnlistener_new_bind(base,         //事件集合
                            listener_cb,                    //当有链接时调用的函数
                            NULL,                           //回调函数的参数
                            LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
                            10,                             //监听队列的长度
                            (struct sockaddr*)&server_addr, //tcp绑定信息
                            sizeof(server_addr)             //tcp信息的大小
                           );
    if(NULL == listener)
    {
        exit(1);
    }
    //监听集合中的事件
    event_base_dispatch(base);
    
    //释放两个对象
    evconnlistener_free(listener);      //连接对象    
    event_base_free(base);              //释放集合
    
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容