libevent库学习笔记

目录

一. 什么是libevent库

二. 安装libevent库

如果用的Linux系统,安装方式如下:

三. ibevent 核心流程

(1) 初始化 libevent:(类似Reactor机制)

什么是 Reactor?

(2)事件注册:

event_assign ( ) 函数

(3)启动事件循环:循环监听

退出循环监听

(4)事件处理阶段

(5)资源释放阶段

四. bufferevent

1.bufferevent功能:

 2.bufferevent核心结构

(1)创建bufferevent

(2)释放Bufferevent 

(3)禁用或启动回调函数

(4)设置bufferevent回调函数和相关设置

 水位设置

(5)在基于套接字的bufferevent上启动连接

(6)操控bufferevent中的数据

获取缓冲区函数:

写入函数:

输出函数:

五.Evbuffer

(1)创建和销毁Evbuffer

(2)向buffer中添加数据

(3)读取数据

(4)缓冲区管理

(5)删除和移动buffer中的内容

(6)搜索buffer中的内容

(7)面向行的读取

(8)复制数据


一. 什么是libevent库

Libevent 是一个轻量级的开源高性能网络库,主要用于简化事件驱动的网络编程。它封装了底层操作系统的 I/O 多路复用机制(如 epoll、kqueue、select 等),提供统一的 API,帮助开发者高效处理网络事件、定时器和信号。它支持/dev/poll、kqueue、event ports、select、poll和epoll事件机制,也因此它是一个跨操作系统的库(支持Linux、*BSD、Mac OS X、Solaris、Windows等)。

二. 安装libevent库

libevent官网:https://libevent.org/

我下载的是libevent-2.1.11-stable.tar.gz  (一般推荐下载稳定版本Stable Releases,不推荐下最新的一版可能会有一点小bug)

如果用的Linux系统,安装方式如下:

(1)下载时,如果有sudo权限可以把下载的libevent库放到系统路径下(如/usr/bin),如果没有权限可以把下载的libevent库放到用户路径下(如/$HOME/local  自己文件路径)

(2) 在Linux系统上下载压缩包

wget https://github.com/libevent/libevent/releases/download/release-2.1.11-stable/libevent-2.1.11-stable.tar.gz

(3)解压

tar -xzf libevent-2.1.11-stable.tar.gz

(4)cd 到解压完成后的文件中

cd libevent-2.1.11-stable

(5)编译安装

没有sudo权限(需要改环境配置,因为你安装在自己的用户文件里,是不在默认的库路径下的,你需要自己添加,否则系统在使用库的时候只会去默认路径下找,会找不到你安装的库)

注意:我这里的环境配置是临时生效的,只在本机可以,如果别人也登录这个Linux系统是没有办法生效的,想永久生效用后面的(~/.bashrc 是用户级的 shell 配置文件

./configure --prefix=$HOME/local  # 指定用户目录
make
make install  # 无需sudo


#环境配置(临时生效)
export PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH="$HOME/local/lib:$LD_LIBRARY_PATH"

#环境配置(永久生效)
echo 'export LD_LIBRARY_PATH="$HOME/local/lib:$LD_LIBRARY_PATH"' >> ~/.bashrc

有sudo权限 

sudo ./configure --prefix=/usr/local  # 默认系统路径
sudo make
sudo make install  # 需要sudo

三. ibevent 核心流程

(1) 初始化 libevent:(类似Reactor机制)

事件初始化:创建一个event_base对象,这是 libevent 的基础,用于管理事件集合。

// 创建
struct event_base * event_base_new(void);

// 销毁释放
event_base_free(struct event_base *)

// 如果fork出子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化
int event_reinit(struct event_base *base);

底层实现event_base 会自动检测当前系统支持的最佳 I/O 多路复用机制(如 epoll、kqueue 等),并初始化事件循环的上下文。Linux下一般会用epoll模型。

用下面这个命令可以获取IO模型的名称。

const char *event_base_get_method(const struct event_base *base);

什么是 Reactor?

定义:一种事件驱动的设计模式,用于处理并发 I/O 操作。

核心思想
“不要主动等待事件,而是等事件通知你”
将事件监听和事件处理分离,通过回调机制实现异步响应。

(2)事件注册:

  event_base是事件的集合,负责事件的循环,以及集合的销毁。而event就是event_base中的基本单元:事件。

  创建新的 event 结构,指定事件对应的文件描述符(fd)、事件类型(如读事件EV_READ、写事件EV_WRITE)、回调函数以及回调函数的参数等。然后通过event_add()函数将事件添加到event_base的事件队列中,此时事件进入等待激活状态。event_new生成的事件是在堆上分配的内存。

//创建一个事件event
struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg);

//将事件添加到 event_base 的事件集合中
int event_add(struct event *ev, const struct timeval *tv);

当一个事件通过event_add被注册到event_base上的时候,这个事件处于pending(等待状态)
当只有有事件进来的时候,event才会被激活active状态,相关的回调函数就会被调用。

事件类型

EV_READ:监听 fd 可读事件(如客户端发送数据)。

EV_WRITE:监听 fd 可写事件(如发送缓冲区可写入数据)。

EV_PERSIST:持续监听事件(触发后不自动删除)。

  • base:即event_base
  • fd:文件描述符。
  • what:event关心的各种条件。
  • cb:回调函数。
  • arg:用户自定义的数据,可以传递到回调函数中去。

event_assign ( ) 函数

event_new每次都会在堆上分配内存。有些场景下并不是每次都需要在堆上分配内存的,这个时候我们就可以用到event_assign方法。

已经初始化或者处于 pending 的 event,首先需要调用 event_del()删除这个事件,后再调用 event_assign()。这个时候就可以重用这个event了。

//函数会将事件删除,确保不再监听这个事件后续才能用event_assign()重新分配参数
int event_del(struct event *event);

// 此函数用于初始化 event(包括可以初始化栈上和静态存储区中的 event)
// event_assign() 和 event_new() 除了 event 参数之外,使用了一样的参数
// event 参数用于指定一个未初始化的且需要初始化的 event
// 函数成功返回 0 失败返回 -1
int event_assign(struct event *event, struct event_base *base,evutil_socket_t fd, short what,void (*callback)(evutil_socket_t, short, void *), void *arg);
     
// 类似上面的函数,此函数被信号 event 使用
event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)

(3)启动事件循环:循环监听

  调用event_base_dispatch()函数启动事件循环,该函数内部实际调用event_base_loop()函数。在event_base_loop()中,会先检查是否有信号标记,如果有则调用信号的回调函数。接着根据定时器最小时间,设置 I/O 多路复用的最大等待时间,然后调用选定的 I/O 多路复用机制的dispatch函数(如 epoll 的epoll_dispatch)监听事件,将活跃事件添加到活跃事件链表中。同时,检查定时事件,将就绪的定时事件从小根堆中删除,插入到活跃事件链表中。

简单来说他类似于while(1)的功能,去循环监视事件的发生。

//调用该函数,相当于(相当于while(1) {epoll_wait})没有设置标志位的event_base_loop。
//程序将会一直运行,直到没有需要检测的事件了,或者被结束循环的api终止。
int event_base_dispatch(struct event_base *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);

如果参数填了0,则只有事件进来的时候才会调用一次事件的回调函数,比较常用

EVLOOP_ONCE(0x01):执行单次事件循环:

  • 处理所有就绪事件
  • 若设置了超时事件,则等待到第一个超时事件触发
  • 处理完所有就绪事件或超时事件后立即返回

EVLOOP_NONBLOCK(0x02):非阻塞模式:

  • 检查是否有就绪事件,若无则立即返回
  • 不等待任何超时事件,即使已设置超时

EVLOOP_NO_EXIT_ON_EMPTY(0x04):循环不退出:

  • 即使没有活动事件(如无监听的 fd 或超时事件),也不退出事件循环
  • 需手动调用 event_base_loopexit() 终止循环

退出循环监听

//两个函数的区别是如果正在执行激活事件的回调函数
//将在事件回调执行结束后终止循环(如果tv时间非NULL,那么将等待tv设置的时间
  后立即结束循环)
int event_base_loopexit(struct event_base *base, const struct timeval *tv);

//立即终止循环
int event_base_loopbreak(struct event_base *base);
struct timeval {
	long    tv_sec;                    
	long    tv_usec;

(4)事件处理阶段

对于每个被激活的事件,libevent 会调用用户注册的回调函

回调函数执行完成后,根据事件类型决定是否重新添加该事件:

   1.持久化事件(EV_PERSIST):不会自动移除

  如果event_new中的what参数选择了EV_PERSIST,则是持久的类型。持久的类型调用完回调函数后,会继续转为pending状态,就会继续等待事件进来,后续相同类型的事件会继续触发回调。大部分情况下会选择持久类型的事件。

2. 非持久化事件:继续保持监听

  事件触发(回调执行)后,就会变成初始化的状态。,会自动变成 non-pending(非等待)状态,相当于被 event_del() 了。 如果希望继续监听该事件,需要调用event_add 继续将事件注册到event_base上之后才能使用。

(5)资源释放阶段

最后结束时一定要释放资源,释放事件资源 和 event_base 资源

void event_free(struct event *event);

注意:

  1. 实际上,write_cb() 是用来响应数据发送完成事件的,真正写入数据的操作是通过 bufferevent_write() 完成的。

四. bufferevent

   bufferevent 是 libevent 中提供的一种高级 I/O 抽象,建立在 evbuffer 之上,用于管理 带缓冲的事件驱动 I/O。它简化了异步 I/O 的管理,使得开发者在处理网络数据时可以更加方便地进行读取、写入和处理错误。bufferevent 背后通过结合eventloop事件循环、evbuffer 以及底层 I/O 操作,形成一个结构化的 I/O 流管理方式。

1.bufferevent功能:

  1. 异步 I/O 操作:支持异步读写操作,不会阻塞主线程。
  2. 双缓冲机制:分别维护读缓冲区和写缓冲区,读写操作使用独立的缓冲区 (evbuffer) 来管理数据。
  3. 自动管理事件:自动处理 I/O 事件,如数据可读、可写、错误处理等,无需手动管理底层事件。
  4. 读写超时:支持超时机制,用户可以设置读取或写入超时。
  5. 高级控制:提供读写暂停、调整缓冲区大小等高级功能。

 2.bufferevent核心结构

(1)创建bufferevent

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

参数:

  1. base:即event_base
  2. fd:文件描述符。如果是socket的方法,则socket需要设置为非阻塞的模式。
  3. options:行为选项,下面是行为选项内容:
  • BEV_OPT_CLOSE_ON_FREE :当 bufferevent 被释放同时关闭底层(socket 被关闭等) 一般用这个选项
  • BEV_OPT_THREADSAFE :为 bufferevent 自动分配锁,这样能够在多线程环境中安全使用
  • BEV_OPT_DEFER_CALLBACKS : 当设置了此标志,bufferevent 会延迟它的所有回调(参考前面说的延时回调)
  • BEV_OPT_UNLOCK_CALLBACKS : 如果 bufferevent 被设置为线程安全的,用户提供的回调被调用时 bufferevent 的锁会被持有。如果设置了此选项,Libevent 将在调用你的回调时释放 bufferevent 的锁

(2)释放Bufferevent 

void bufferevent_free(struct bufferevent *bev);

如果设置了延时回调BEV_OPT_DEFER_CALLBACKS,则释放会在延时回调调用了回调函数之后,才会真正释放。

(3)禁用或启动回调函数

可以启用或者禁用 bufferevent 上的 EV_READ(读)、EV_WRITE(写) 或者 EV_READ | EV_WRITE 事件。没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。

//开启监听事件(读,写,)
void bufferevent_enable(
	struct bufferevent *bufev, 
	short events
); 
 
//禁用, 对应的回调就不会被调用
void bufferevent_disable(
	struct bufferevent *bufev, 
	short events
); 
  
short bufferevent_get_enabled(
	struct bufferevent *bufev
); 

例如像下面这样使用: 

// 启用读取和写入事件
bufferevent_enable(bev, EV_READ | EV_WRITE);

// 禁用写入事件(仅读取)
bufferevent_disable(bev, EV_WRITE);

(4)设置bufferevent回调函数和相关设置

使用bufferevent之后,libevent会帮我们托管三种事件:
1、读取事件
2、写入事件
3、处理事件
这三种事件都在回调的函数结构中体现!!

//用于设置对应的具体的回调函数
void bufferevent_setcb(
    struct bufferevent *bev,
    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内部缓冲区是一个2米深的水池,假设这个水池里已经有1米深的水了,低水位lowmark相当于进水口,高水位highmark相当于出水口,我现在想要达到1.5米深的水后进去游泳,1.5米就是低水位,游泳事件就是达到低水位后我执行的回调函数,水池加水最多只能加到2米深,超过2米会溢出水池,2米就是高水位标记。

可以批量处理数据:避免频繁回调,等数据积累到一定量再处理,防止缓冲区溢出

void bufferevent_setwatermark(
    struct bufferevent *bufev,  // bufferevent指针
    short events,              // 事件类型
    size_t lowmark,            // 低水位标记
    size_t highmark            // 高水位标记
);

 

(5)在基于套接字的bufferevent上启动连接

如果bufferevent的套接字还没有连接上,可以启动新的连接。

//如果连接启动成功,函数返回0;发生错误则返回-1
int bufferevent_socket_connect(
    struct bufferevent *bev,
    struct sockaddr *address,
    int addrlen);

(6)操控bufferevent中的数据

注意:下面涉及到的evbuffer后面会讲

  • 获取缓冲区函数:
//获取 bufferevent 的输入缓冲区(用于读取接收到的数据)
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);

//获取 bufferevent 的输出缓冲区(用于写入待发送的数据)
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
  • 写入函数:

bufferevent_write()从bufferevent的输出缓冲区添加(写)数据
将内存中从data处开始的size字节数据添加到输出缓冲区的末尾

bufferevent_write_buffer() 移除buf的所有内容,将其放置到输出缓冲区的末尾。

//都是返回值:成功0,失败-1
//向 bufferevent 的输出缓冲区写入原始数据。
int bufferevent_write(struct bufferevent *bufev,const void *data, size_t size);

//将数据从 evbuffer 写入到 bufferevent 的输出缓冲区。       
int bufferevent_write_buffer(struct bufferevent* bufev,struct evbuffer* buf);
  • 输出函数:
//从 bufferevent 的输入缓冲区读取数据。
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); 

//将 bufferevent 输入缓冲区的数据读取到指定的 evbuffer中。
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);

 

五.Evbuffer

Evbuffer是 libevent 提供的高效内存缓冲区管理结构,用于网络编程中的数据缓存和流式处理,Evbuffer给我们提供了非常实用的IO缓存工具。

struct evbuffer 是 libevent 内部用于管理动态缓冲区的核心数据结构。

struct evbuffer{  
  u_char *buffer;        // 当前有效缓冲区的内存起始地址 
  u_char *orig_buffer;   // 整个分配(realloc)用来缓冲的内存起始地址 
  size_t misalign;   // origin_buffer和buffer之间的字节数
  size_t totallen;   // 整个分配用来缓冲的内存字节数
  size_t off;   // 当前有效缓冲区的长度(字节数)
  void (*cb)(struct evbuffer *, size_t, size_t, void *);  //回到函数,当缓冲区有变化的时候会被调用
  void *cbarg;   //回调函数的参数
}; 

 

(1)创建和销毁Evbuffer

//创建新的 evbuffer
struct evbuffer *evbuffer_new(void)	
//释放 evbuffer
void evbuffer_free(struct evbuffer *buf)	

(2)向buffer中添加数据

libevent的缓冲是一个连续的内存区域,其处理数据的方式(写数据和读数据)更像一个队列操作方式:从后写入,从前读出。

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

//格式化写入(类似 sprintf)
int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)	

//(移动数据)将一个 evbuffer(源缓冲区)的全部内容移动到另一个 evbuffer(目标缓冲区),并清空源缓冲区。
//dst:目标缓冲区,src:源缓冲区
int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src)	

//将外部内存区域的引用添加到 evbuffer,而非复制数据.当 evbuffer 不再需要该数据时,会调用清理函数释放资源。
//buf:目标缓冲区,将添加数据引用    data:指向外部数据的指针
int evbuffer_add_reference(struct evbuffer *buf, const void *data, size_t datlen, evbuffer_ref_cleanup_cb cleanupfn, void *arg)

 下面这两个函数用于向 evbuffer 的头部插入数据

//将指定数据 (data) 插入到 evbuffer 的头部(首部)
//逆向evbuffer_add()
int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size);

//将 src 缓冲区的数据全部插入到 dst 缓冲区的头部
//逆向evbuffer_add_buffer
int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);

 

(3)读取数据

//读取数据(并移除)
size_t evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen)	

//移动数据到另一个 buffer
int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst, size_t datlen)	

//零拷贝访问数据(直接获取指针)
evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len, struct evbuffer_ptr *start_at, struct evbuffer_iovec *vec_out, int n_vec)	

(4)缓冲区管理

//返回缓冲区当前数据量,返回的是buffer中的字节数
size_t evbuffer_get_length(const struct evbuffer *buf)	

//丢弃前 len 字节
int evbuffer_drain(struct evbuffer *buf, size_t len)

//预分配内存(避免多次扩容)
//1.计算所需总空间:当前已用空间 + datlen。
//2.检查现有容量:如果当前容量已足够,则不进行任何操作。
//3.扩展缓冲区:
//若容量不足,尝试分配更大的内存块。
//通常会按指数方式增加容量(例如翻倍),以减少未来重新分配的次数。
//将原有数据复制到新的内存区域。
int evbuffer_expand(struct evbuffer *buf, size_t datlen)	

(5)删除和移动buffer中的内容

//从buf前面复制和移除datlen字节到data处的内存中
//如果可用字节少于datlen,函数复制所有字节。失败时返回-1,否则返回复制了的字节数
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);  

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

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

(6)搜索buffer中的内容

//是 libevent 提供的 缓冲区位置指针,用于在 evbuffer 中随机访问数据
//pos为偏移量,如果为-1则没查询到,大于-1,则搜索到了匹配的位置
//函数返回包含字符串位置,或者在没有找到字符串时包含-1的evbuffer_ptr结构体。
struct evbuffer_ptr {  
        ev_ssize_t pos;  
        struct {  
                /* internal fields */  
        } _internal;  
};  

//查找,在缓冲区中查找含有len个字符的字符串what
//如果提供了start参数,则从指定的位置开始搜索;否则,从开始处进行搜索。
struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start);  

//和evbuffer_search差不多,只是它只考虑在end之前出现的what。
struct evbuffer_ptr evbuffer_search_range(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start, const struct evbuffer_ptr *end);  

//用于在evbuffer中查找行终止符
struct evbuffer_ptr evbuffer_search_eol(struct evbuffer *buffer, struct evbuffer_ptr *start, size_t *eol_len_out, enum evbuffer_eol_style eol_style);  

(7)面向行的读取

//从evbuffer前面取出一行,用一个新分配的空字符结束的字符串返回这一行
char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style);

enum evbuffer_eol_style {  
        EVBUFFER_EOL_ANY,          //通用行结束符,支持 \n(LF)、\r\n(CRLF)、\r(CR)
        EVBUFFER_EOL_CRLF,         // \r\n 或者\n 视为行结束符,
        EVBUFFER_EOL_CRLF_STRICT,  //必须为 \r\n,否则视为无效
        EVBUFFER_EOL_LF,           //也就是\n,ASCII值是0x0A
        EVBUFFER_EOL_NUL           //将 \0(NULL 字符)视为行结束符。
};  

(8)复制数据

//从缓冲区 头部 拷贝指定长度的数据到目标内存,不修改缓冲区内容。
ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data, size_t datlen)	

//从 指定位置(pos)拷贝数据到目标内存,不修改缓冲区内容
ev_ssize_t evbuffer_copyout_from(struct evbuffer *buf,const struct evbuffer_ptr *pos,void *data_out,size_t datlen);

正在学习中,如果有错敬请指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值