[C/C++后端开发学习]16 使用libevent

libevent和libev

libevent和libev均是基于C语言实现的异步事件库,它们封装了事件管理过程中与操作系统的交互,使得调用者无需关注事件监测机制以及IO处理机制等在不同平台上的差异,而只需关注事件的业务逻辑处理;同时也提供了方便易用的事件管理API,调用者无需关注底层实现细节。其基本工作流程大致为:

  • 注册异步事件
  • 检测异步事件是否就绪,包括定时任务是否就绪
  • 根据事件的触发顺序以及优先级,调用对应的回调函数进行事件处理

可以处理的事件类型包括:网络I/O事件、定时事件以及信号事件。

但 libevent 存在一些设计缺陷:

大量使用全局变量,导致在多线程环境中很难安全地使用 libevent;event 的数据结构设计得太大了,IO事件、定时事件以及信号处理等等相关的变量全封装在一个结构体中;其中的组件如 http、dns、openssl 等实现的质量差,也存在计时器不精确的问题。

libev 则是对 libevent 进行了改进:完全去除了全局变量的使用,通过回调传参来传递上下文;根据不同事件类型来构建不同的数据结构,从而减低不同事件的耦合性。

libevent的GitHub仓库地址为 libevent,按照说明编译安装后即可使用。
libev的仓库地址为enki/libev

libevent的封装层次

可以从两个层次上来使用libevent,一是使用较低的封装层次,由libevent完成事件的检测,然后调用者自己完成IO操作,类似于直接使用epoll。该层次封装了事件管理器的操作和事件本身的操作接口:

在这里插入图片描述
如果你不想自己管理I/O,希望由libevent库完成读写I/O的处理后自己仅需从缓冲区中读数据来完成事件的逻辑处理,那么可以从较高的封装层次上来使用libevent。它提供了关于网络I/O的连接事件、读写事件等等的事件操作接口,调用者需要做的仅仅是注册这些事件并且在事件处理函数被回调时从相应的buffer中读写业务数据。除了I/O事件,libevent还提供对定时事件和信号处理事件的管理,十分方便。
在这里插入图片描述

libevent 底层具体使用哪一种 IO多路复用机制取决于struct eventop *eventops[]这个全局数组,在Linux下默认首选epoll。因此事件管理的主循环event_base_loop底层实际就是调用了epoll_wait()

所有从epoll_wait()返回的就绪事件以及定时器超时的事件都会被加入到就绪队列struct evcallback_list *activequeues;,在event_base_loop()-->event_process_active()中进行统一处理,即调用对应事件处理的回调函数。

evutil.c中还封装了很多工具函数用于设置socket的属性,如evutil_make_socket_nonblocking()可设置socket为非阻塞,evutil_make_listen_socket_reuseable()可设置SO_REUSEADDR,等等。

使用libevent

1)由调用者自己管理I/O

从较低层次上使用 libevent 需要包含头文件<event2/event.h>

首先需要创建一个 event base,它是事件管理器工作的基础,如果以默认的配置创建,则调用event_base_new()即可,使用event_base_new_with_config()则允许传入其他配置。event_base负责检测哪些已注册的事件处于就绪状态。

其次是注册需要由 event base 进行监管的事件,使用event_new()创建一个事件,并通过event_add()注册到对应的event_base

最后是进行事件的 dispatch 过程,具体就是执行event_base_dispatch()开启一个大循环,然后执行各个就绪事件的回调函数。回调函数的签名为:void (*)(evutil_socket_t, short, void *)

示例代码如下:

// 开启 socket监听,非重点
int socket_listen(int port) {
   
    evutil_socket_t listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0) {
   
        return -1;
    }
    evutil_make_listen_socket_reuseable(listenfd);

    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(port);
    if(bind(listenfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
   
        evutil_closesocket(listenfd);
        return -1;
    }

    if (listen(listenfd, 5) < 0) {
   
        evutil_closesocket(listenfd);
        return -1;
    }
    evutil_make_socket_nonblocking(listenfd);
    return listenfd;
}

int main() {
   
    int listenfd = socket_listen(8888);
    // 开始使用 libevent
    struct event_base* base = event_base_new();
    struct event* ev_listen = event_new(base, listenfd, EV_READ | EV_PERSIST, accept_cb, base);
    event_add(ev_listen, NULL); // 注册事件,如果不需要设置超时时间这第二个参数为 NULL

    event_base_dispatch(base);  // 开启主循环

    event_free(ev_listen);
    event_base_free(base);
    return 0;
}

各个回调函数:

void write_cb(evutil_socket_t fd, short events, void *arg);
void read_cb(evutil_socket_t fd, short events, void *arg) {
   
    char msg[1024];
    struct event *ev = (struct event*)arg;
    int len = recv(fd, msg, sizeof(msg) - 1, 0);
    if(len <= 0) {
   
        printf("client disconnect or error.\n");
        event_free(ev);
        close(fd);
        return ;
    }

    msg[len] = '\0';
    printf("recv msg: %s\n", msg);
    event_del(ev);
    event_assign(ev, event_get_base(ev), fd, EV_WRITE | EV_PERSIST, write_cb, ev);
    event_add(ev, NULL);
}

void write_cb(evutil_socket_t fd, short events, 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值