当前位置: 首页 > news >正文

【libevent_libev】libevent_libev

服务端事件组成

1 网络io事件

  • linux:epoll、poll、select
  • mac:kqueue
  • window:iocp

libevent/libev 对 epoll/poll/select/kqueue/icop等进行了封装,根据不同平台下选择不同的多路复用IO接口。

2 定时事件

  • 红黑树
  • 最小堆:二叉树、四叉树
  • 跳表
  • 时间轮

libevent/libev 对定时事件数据结构等进行了封装。libevent的定时事件数据结构是二叉树最小堆;libev的定时事件数据结构是四叉树最小堆。

3 信号事件

libevent 与 libev 概述

概念

libevent和libev都是c语言实现的异步事件库;通过注册异步事件,库检测事件触发,从而库根据发生事件的先后顺序,调用相应回调函数进行处理;
事件包括:网络io事件,定时事件,信号事件;
事件循环:等待并分发事件;用于管理事件;
libevent 和 libev 主要封装了异步事件库与操作系统的交互(主要对epoll的封装、定时事件、信号事件的统一管理,所有对外的事件接口是异步接口);让用户不用关注平台的差异,只需着手事件的具体处理;
libevent 和 libev 对window支持都比较差,因此产生 libuv 库,libuv 基于 libev,但是 window上封装了 iocp;node.js基于libuv;

区别

从设计理念出发,libev 是为了改进 libevent 中的一些架构决策,例如,libevent 中全局变量的使用使得在多线程环境中很难安全地使用,watcher 的数据结构设计太大,因为它们将 I/O、时间和信号处理放在一个结构体中,额外的组件如 http 、dns、openssl, 服务器由于实现质量差以及由此产生的安全问题,计时器不精确,不能很好地处理时间事件。
libev 通过不使用全局变量,而是对所有回调函数传参的方式传递上下文;并且根据不同事件类型构建不同的数据结构,这样以来减低事件的耦合性;
libevent的定时事件管理的数据结构是二叉树最小堆;libev的定时事件管理的数据结构是四叉树最小堆,经过验证在>5000数量级,四叉树最小堆比二叉树最小堆效率高5%。
libev 小而高效;只关注事件处理;而libevent集成一些openssl、http协议解析等额外的组件。

libevent

编译

aclocal
libtoolize --force
autoheader
automake --add-missing
autoconf
./configure && make && make install

特色

bufferevent

提供 bufferevent,进一步提供管理读写事件(包括连接断开事件),以及读写数据缓冲(bufferevent里封装了每个tcp连接的用户层读/写缓冲区);

  • bufferevent_socket_new
  • bufferevent_socket_connect
  • bufferevent_free
  • bufferevent_setcb
  • bufferevent_enable
  • bufferevent_disable
  • bufferevent_get_input //读缓冲区
  • bufferevent_get_output //写缓冲区
  • bufferevent_write
  • bufferevent_write_buffer
  • bufferevent_read
  • bufferevent_read_buffer

evconnlistener

提供了监听和接受 tcp 连接的方法

  • evconnlistener_new
  • evconnlistener_new_bind
  • evconnlistener_free
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);

struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd);

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

/** Flag: Indicates that we should not make incoming sockets nonblocking
* before passing them to the callback. */
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING (1u<<0)
/** Flag: Indicates that freeing the listener should close the underlying
* socket. */
#define LEV_OPT_CLOSE_ON_FREE (1u<<1)
/** Flag: Indicates that we should set the close-on-exec flag, if possible */
#define LEV_OPT_CLOSE_ON_EXEC (1u<<2)
/** Flag: Indicates that we should disable the timeout (if any) between when
* this socket is closed and when we can listen again on the same port. */
#define LEV_OPT_REUSEABLE (1u<<3)
/** Flag: Indicates that the listener should be locked so it's safe to use
* from multiple threadcs at once. */
#define LEV_OPT_THREADSAFE (1u<<4)
/** Flag: Indicates that the listener should be created in disabled
* state. Use evconnlistener_enable() to enable it later. */
#define LEV_OPT_DISABLED (1u<<5)
/** Flag: Indicates that the listener should defer accept() until data is
* available, if possible. Ignored on platforms that do not support this.
*
* This option can help performance for protocols where the client transmits
* immediately after connecting. Do not use this option if your protocol
* _doesn't_ start out with the client transmitting data, since in that case
* this option will sometimes cause the kernel to never tell you about the
* connection.
*
* This option is only supported by evconnlistener_new_bind(): it can't
* work with evconnlistener_new_fd(), since the listener needs to be told
* to use the option before it is actually bound.
*/
  #define LEV_OPT_DEFERRED_ACCEPT (1u<<6)
  /** Flag: Indicates that we ask to allow multiple servers (processes or
* threads) to bind to the same port if they each set the option.
*
* SO_REUSEPORT is what most people would expect SO_REUSEADDR to be, however
* SO_REUSEPORT does not imply SO_REUSEADDR.
*
* This is only available on Linux and kernel 3.9+
*/
  #define LEV_OPT_REUSEABLE_PORT (1u<<7)

主要接口

event_base_new

初始化 libevent;对应理解 epoll_create

struct event_base *event_base_new(void);

event_new

创建事件,初始化event和相应的回调函数

struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, 
			void (*cb)(evutil_socket_t, short, void *), void *arg)

event_set

设置事件

void
event_set(struct event *ev, evutil_socket_t fd, short events,
			void (*callback)(evutil_socket_t, short, void *), void *arg)

event_base_set

建立 event 与 event_base 的映射关系;

int event_base_set(struct event_base *eb, struct event *ev);

event_add

注册事件,包括时间事件;相当于 epoll_ctl;

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

event_del

注销事件

int
event_del(struct event *ev)

event_base_loop

进入事件循环

int
event_base_loop(struct event_base *base, int flags)

注意
event_new 相当于 malloc + event_set + event_base_set

示例代码

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include "event2/event.h"

void socket_read_cb(int fd, short events, void *arg);

void socket_accept_cb(int fd, short events, void* arg)
{
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    evutil_socket_t clientfd = accept(fd, (struct sockaddr*)&addr, &len );
    evutil_make_socket_nonblocking(clientfd);
    printf("accept a client %d\n", clientfd);
    struct event_base* base = (struct event_base*)arg;
    struct event *ev = event_new(NULL, -1, 0, NULL, NULL);
    event_assign(ev, base, clientfd, EV_READ | EV_PERSIST,
                 socket_read_cb, (void*)ev);
    event_add(ev, NULL);
}
 
void socket_read_cb(int fd, short events, void *arg)
{
    char msg[4096];
    struct event *ev = (struct event*)arg;
    int len = read(fd, msg, sizeof(msg) - 1);
    if( len <= 0 )
    {
        printf("client fd:%d disconnect\n", fd);
        event_free(ev);
        close(fd);
        return;
    }
 
    msg[len] = '\0';
    printf("recv the client msg: %s", msg);
 
    char reply_msg[4096] = "recvieced msg: ";
    strcat(reply_msg + strlen(reply_msg), msg);
    write(fd, reply_msg, strlen(reply_msg));
}
 
int socket_listen(int port)
{
    int errno_save;
 
    evutil_socket_t listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
        return -1;
 
    evutil_make_listen_socket_reuseable(listenfd);
 
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    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 argc, char** argv)
{
    int listenfd = socket_listen(8989);
    if (listenfd == -1)
    {
        printf("socket_listen error\n");
        return -1;
    }
 
    struct event_base* base = event_base_new();
 
    struct event* ev_listen = event_new(base, listenfd, EV_READ | EV_PERSIST,
                                        socket_accept_cb, base);
    event_add(ev_listen, NULL);
 
    event_base_dispatch(base);
 
    return 0;
}

/*
gcc main.c -o main -I../libevent/include -L../libevent/.libs -levent
client:
    telnet 127.0.0.1 8989
*/

libev

libev的主要数据结构

EV_WATCHER

/* shared by all watchers */
#define EV_WATCHER(type) \\
	int active; 	/* 表示 watcher 是否活跃,active = 1 表示还没被 stop 掉 */ \\
	int pending; 	/* 存储 watcher 在 pendings 中的索引。大于零表示还没被处理。
				 	 * watcher 的回调函数被调用后,会设置为 0。 */ \\
	int priority; 	/* 事件的优先级 */ \\
	void *data; 	/* 回调函数所需要的数据 */ \\
	void (*cb)(EV_P_ struct type *w, int revents); /* 回调函数 */
作用:不同事件类型的共有信息。

EV_WATCHER_LIST

#define EV_WATCHER_LIST(type) \\
	EV_WATCHER (type) \\
	struct ev_watcher_list *next; /* 同一个文件描述符上可以被注册多个 watcher,比如:监听
是否可读/可写 */
作用:watcher 链表

ev_io

typedef struct ev_io
{
	EV_WATCHER_LIST (ev_io)
	int fd;
	int events;
} ev_io;
// 作用:记录 IO 事件的基本信息。
// ev_io 相比 ev_watcher 增加了 next, fd, events 的属性。

ANFD

/* file descriptor info structure */
typedef struct
{
	WL head; /* 同一个 fd 上的所有 ev_watcher 事件 */
	unsigned char events; 	/* the events watched for,通常被设置成所有 ev_watcher->events 的或集。 */
	unsigned char reify; 	/* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET)
						  	 * 默认值为 0,当调用 ev_io_start 后,reify 会被设置为 `w->events & EV__IOFDSET | EV_ANFD_REIFY`。
						  	 * 如果 reify 未被设置,则把 fd 添加到 fdchanges 中去。*/
	...
} ANFD;
// 作用:
// 解决根据 fd 快速找到与其相关的事件。
// libev 的方法是用 anfds 数组来存所有 fd 信息的结构体,然后以 fd 值为索引直接找到对应的结构体。

ANPENDING

/* stores the pending event set for a given watcher */
typedef struct
{
	W w;
	int events; /* the pending event set for the given watcher */
} ANPENDING;
// 作用:存储已准备好的 watcher,等待回调函数被调用。

ev_loop

struct ev_loop {
	double ev_rt_now; /* 当前的时间戳 */
	
    int backend; /* 采用哪种多路复用方式, e.g. SELECT/POLL/EPOLL */
	int activecnt; /* total number of active events ("refcount") */
	int loop_done; /* 事件循环结束的标志,signal by ev_break */
	
    int backend_fd; /* e.g. epoll fd, created by epoll_create*/
	void (*backend_modify)(EV_P_ int fd, int oev, int nev)); /* 对应 epoll_ctl */
	void (*backend_poll)(EV_P_ ev_tstamp timeout)); /* 对应 epoll_wait */

    void (*invoke_cb)(struct ev_loop *loop);

    ANFD *anfds; /* 把初始化后的 ev_io 结构体绑定在 anfds[fd].head 事件链表上,方便根据 fd 直接查找。*/
	
    int *fdchanges; /* 存放需要 epoll 监听的 fd */
	ANPENDING *pendings [NUMPRI]; /* 存放等待被调用 callback 的 watcher */
}
// 作用:基本包含了 loop 循环所需的所有信息,为让注释更容易理解采用 epoll 进行说明。

libev的主要接口

ev_io_init

初始化 watcher 的 fd/events/callback

#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev), (fd),(events)); } while (0)

ev_io_start

注册并绑定 io watcher 到 ev_loop

void ev_io_start(struct ev_loop *loop, ev_io *w)

ev_timer_start

注册并绑定 timer watcher 到 ev_loop

void ev_timer_start(struct ev_loop *loop, ev_timer *w)

ev_run

开启改 ev_loop 的事件循环

int ev_run(struct ev_loop *loop, int flags)

示例代码

使用libev风格代码:

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>

#include <string.h>
#include <time.h>
#include "ev.h"

static void
do_accept(struct ev_loop* reactor, ev_io* w, int events) {
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    socklen_t len = sizeof(addr);
    int clientfd = accept(w->fd, (struct sockaddr *)&addr, &len);
    if (clientfd != -1)
        close(clientfd);

    printf("accept fd = %d\n", clientfd);
}

int socket_listen(uint16_t port) {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listenfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1)
        return -1;

    if (listen(listenfd, 5) == -1)
        return -1;

    printf("server start listening port:%d\n", port);
    return listenfd;
}

static void
do_timer(struct ev_loop *loop, ev_timer *timer_w, int e) {
    time_t now = time(NULL);
    printf("do_timer %s", (char*)ctime(&now));
    // ev_timer_stop(loop, timer_w);
}

int main() {
    struct ev_loop *loop = ev_loop_new(0);

    struct ev_timer t1;
    ev_timer_init(&t1, do_timer, 1, 1);
    ev_timer_start(loop, &t1);

    int listenfd = socket_listen(8989);
    if (listenfd == -1) return -2;
    struct ev_io i1;
    ev_io_init(&i1, do_accept, listenfd, EV_READ);
    ev_io_start(loop, &i1);
    ev_run(loop, 0);

    ev_loop_destroy(loop);
    return 0;
}

/*
gcc main-ev.c -o main-ev -I../libev/ -L../libev/.libs/ -lev
*/

使用libevent风格代码:
注意,本质上还是使用libev库,只是libev为了兼容libevent的代码,进行了一层封装,具体封装细节在头文件在"/libev/event.h"中。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <string.h>
#include <time.h>
#include "event.h"

static struct event evtimer;

static void
do_accept(int fd, short events, void* arg) {
    struct event_base *ev = (struct event_base *)arg;
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    socklen_t len = sizeof(addr);
    int clientfd = accept(fd, (struct sockaddr *)&addr, &len);
    if (clientfd != -1)
        close(clientfd);

    printf("accept fd = %d\n", clientfd);
}

static void
do_timer(int fd, short events, void* arg) {
    time_t now = time(NULL);
    printf("do_timer %s", (char*)ctime(&now));
    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    //因为libevent版本的回调函数参数无法获得event所属的哪个,所以需要做成evtimer全局变量
    event_add(&evtimer, &tv);
}

int socket_listen(uint16_t port) {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listenfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1)
        return -1;

    if (listen(listenfd, 5) == -1)
        return -1;

    printf("server start listening port:%d\n", port);

    return listenfd;
}

int main() {
    
    struct event_base* ev = event_init();

    struct event evlisten;

    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;

    int listenfd = socket_listen(8989);
    if (listenfd == -1) return -2;
    
    event_set(&evlisten, listenfd, EV_READ | EV_PERSIST, do_accept, ev);
    event_base_set(ev, &evlisten);
    event_add(&evlisten, NULL);

    event_set(&evtimer, -1, 0, do_timer, ev);
    event_base_set(ev, &evtimer);
    event_add(&evtimer, &tv);

    event_base_dispatch(ev);
    
    event_base_free(ev);
    return 0;
}

/*
// gcc main-event.c -o main-event -I../libev/ -L../libev/.libs/ -lev
*/

相关文章:

  • 如何选择和使用腾讯云服务器的方法新手教程
  • 高仿英雄联盟游戏网页制作作业 英雄联盟LOL游戏HTML网页设计模板 简单学生网页设计 静态HTML CSS网站制作成品
  • Figma UI UX设计教程
  • k8s之Pod控制器详解
  • Dcoker入门,小白也学得懂!
  • 微机原理与汇编语言——实验复习
  • windows域控上批量修改域账号密码
  • Redis缓存过期和和内存淘汰策略
  • 基于javaweb的学籍管理系统计算机毕业论文java毕业设计选题源代码
  • Java 基础之线程
  • 【无锁队列】无锁CAS_无锁队列
  • 基于java+ssm+vue+mysql的社区流浪猫狗救助网站
  • ARM 汇编写启动代码之设置栈和调用C语言
  • [论文阅读] 颜色迁移-直方图渐进式颜色迁移
  • 26. SAP ABAP OData Gateway 框架里 /IWFND, /IWBEP 这些缩写代表了什么含义?
  • 简单的CNN实现——MNIST手写数字识别
  • 快速幂及矩阵快速幂
  • HashMap安全嘛? 不安全该怎么办【几种解决方法】【详细】
  • 11.28~12.4日学习总结
  • 如何在 Spring 或 Spring Boot 中使用键集分页
  • HashMap部分源码解析
  • Expedita dolor commodi laborum quos.Distinctio aliquam voluptatum sit.
  • 使用openssl工具生成CSR文件
  • Java 反射总结
  • Kafka(四)- Kafka 生产者
  • Electron结合Vue使用说明
  • 另一种在ARM/x86架构处理器上部署WebDAV服务器的方法
  • Spring Boot使用宝兰德BES进行改造和部署
  • 分享一个你很可能不知道的Java异常实现的缺陷
  • 六 游戏基础知识和SHAPE
  • 湖南2021本科批(普通类历史类)第一次征集志愿投档分数线
  • 2022年甘肃高考482分能报什么大学 482分能上哪些院校
  • 2022年全国各大高校在山东招生计划及分数
  • 浙江有哪些师范大学,年浙江师范类大学分数线排名一览表
  • 武汉设计工程学院是几本
  • 2022感恩节放假吗 中国有哪些节日会放假
  • 12种新高考3+1+2选科组合分析 怎么选科好
  • 0基础艺考最容易过的专业有哪些 通过率最高的专业是什么
  • 体育高水平怎么报名
  • 浙江2022普通类第二段平行投档分数线是多少