libevent
事件处理框架 - event_base
API 函数
在 event_base 底层封装了 IO 多路转接函数, 并且可以对事件进行检测和处理的
// 头文件
#include <event2/event.h>
// 创建一个时间处理框架
struct event_base * event_base_new(void);
// 释放一个事件处理框架
// 参数: even_base_new() 函数的返回值
void event_base_free(struct event_base * base);
// 查看底层支持的 IO 转接模型
const char ** event_get_supported_methods(void);
// 当前使用的 IO 转接模型
const char * event_base_get_method(const struct event_base * base);
事件循环
// 头文件
#include <event2/event.h>
// 启动事件处理框架中的事件循环
// 参数: event_base_new() 返回值
/*
事件检测过程中的特点:
举例: 检测读事件:
- 如果对方没有发送数据, 会一直检测直到触发
- 加你测到有读事件, 默认只会处理一次, 事件循环就终止了, 只能接受一次数据
- 要持续的接收数据, 需要额外设置
*/
// 这个函数被调用, 内部会进行事件检测, 代码阻塞在这一行
// 条件满足, 检测就停止了, 应用程序就结束了
int intevent_base_dispath(struct event_base* base);
- 终止事件循环
// 表示一个时间间隔
struct timeval
{
long tv_sec;
long tv_usec;
}
// 假设事件循环正在继续, 并且正在处理一个有效的事件, 调用这个函数不会马上终止, 在 tv 指定的时长之后, 事件就终止了
int event_base_loopexit(struct event_base * base, const struct timeval * tv)
参数:
- base: 事件处理框架
- tv: 在指定时间段之后, 退出事件循环
// 马上终止循环
int event_base_loopbreak(struct event_base * base);
事件
事件的创建和释放
// 事件处理函数, 被 libevent 框架调用, 实参的指定不是程序员做的 // 程序员可以直接使用回调函数的参数 ==> 需要知道参数的意义 typedef void (* event_callback_fn)(evutil_socket_t fd, short what, void * arg); 参数: - fd: 文件描述符, 是 event_new() 第二个参数 - what: 记录了实际文件描述符触发的事件 - arg: event_new() 的最后一个参数 // 本质是对一个文件描述符进行封装 // 事件被创建之后, 不能被事件处理框架直接检测 struct event_base * event_new(struct event_base * base, evutil_socker_t fd, short what, event_callback_fn cb, void * arg) 参数: - base: 事件处理框架 - fd: 文件描述符, 比如管道的或者套接字的 - what: 要检测的事件, 检测第二个参数 fd 的事件 - EV_READ: 读事件 - EV_WRITE: 写事件 - EV_PERSIST: 设置事件重复检测 - EV_SIGNAL: 信号事件(linux) - EV_ET: 设置边沿模式 - cb: 回调函数, 当检测的事件被触发, 这个函数就被调用 - arg: 作为实参传递给 cd
事件的添加和删除
struct timeval {} // 事件被创建之后, 不能被事件处理框架直接检测, 需要做添加处理 int event_add(struct event * ev, const struct timeval * tv) 参数: - ev: 通过 event_new() 创建得到的事件 - tv: 超时时长, 不用就传 NULL - 检测了一个时间, 并且在 tv 指定的时间段中没有被处罚, 这个事件对应的回调函数会被强制调用 - 如果指定为 NULL, 事件不触发, 对应的回调函数就不被调用 int event_del(struct event * ev);(不常用) 参数: - ev: 通过 event_new() 创建得到的事件
带缓冲区的事件
使用场景: 网络套接字通信
事件处理框架对 bufferevent 的处理方式:
- 不需要调用函数将其添加到事件处理框架上, 没有对应的 add 函数
- bufferevent 对应的事件在事件处理框架上默认是持续检测的
/*
带缓冲区的事件, 名字: bufferevent, 也是一个结构体
带缓冲区的事件结构:
1. 封装了 socket 中的通信的文件描述符 -> int -> 得到了 bufferevent
2. 和原来的区别:
默认的通信的文件描述符:
1. 在内核中对应两块内存, 读缓冲区, 写缓冲区
struct bufferevent 对内核的两块内存进行了封装
1. 使用者不能直接操作内核中的读写缓冲区
2. 在 bufferevent 中又额外提供了两块缓冲区
- 读 -> 内核中的读缓冲区
- 自动将内核读缓冲区中的数据放到 bufferevent 的读缓冲区
- 使用者只需要从 bufferevent 读缓冲区中读数据即可
- 写 -> 内核中的写缓冲区
- 要发送数据, 将数据写入 bufferevent 写缓冲区中即可
3. 读写缓冲区分别对应读写事件
- 当读事件被触发, 读缓冲区对应的回调函数被调用
- 在回调函数中读数据(接收数据)
- 当写事件被触发, 写缓冲区对应的回调函数被调用
- 写缓冲区的数据被写到内核中了(没有什么卵用)
*/
创建/释放基于套接字的 buffevent
// 创建带缓冲区的事件, 起始对通信的套接字进行封装 struct bufferevent * bufferevent_socket_new( struct event_base * base, evutil_socket_t fd, enum bufferevent_options options ) 参数: - base: 事件处理框架 - fd: 通信的文件描述符 - 自己创建通信的文件描述符, 通过函数: socket(); - 自动初始化: -1 - opentions: - BUV_OPT_CLOSE_ON_FREE: 在销毁这个对象的时候, 自动释放封装的文件描述符 // 释放带缓冲区的事件 void bufferevent_free(struct bufferevent * bev);
在 bufferevent 上启动连接
// 连接服务器端的程序, 类似于 connect() 函数 int bufferevent_socket_connect( struct bufferevent * bev, struct sockaddr * address, int addrlen ); 参数: - bev: 带缓冲区的事件, 通过 bufferevent_socket_new() 得到的 - address: 需要在这里初始化服务器端的 IP 和端口信息 - addlen: address 参数执行的内存大小
操作 bufferevent 中的数据
// 数据发送, 其实数据通过这个函数写入 bufferevent 的写缓冲区中 int bufferevent_write(struct bufferevent * bufev, const void * data, size_t size); 参数: - bufev: bufferevent_socket_new() 得到的返回值, 封装了通信的文件描述符 - data: 要发送的数据 - size: data 的长度 返回值: - > 0: 成功 - -1: 失败 // 数据接收, 其实数据通过这个函数从 bufferevent 的读缓冲区中读出的 int bufferevent_read(struct bufferevent * bufev, void * data, size_t size); 参数: - bufev: bufferevent_socket_new() 得到的返回值, 封装了通信的文件描述符 - data: 存储数据的内存 - size: data 的大小