概述
文章目录
- Libevent 事件状态
- Libevent 事件处理
Libevent 事件状态
Libevent 有 4 种事件状态,分别是:已初始化(initialized)、待决(pending)、激活(active)、持久的(persistent),这 4 种状态的转换关系如下图所示:
下面比照上图,对这 4 种状态进行说明:
-
已初始化(initialized):此状态对应图中的 non-pending 状态,表示事件已经新建完成,但是还未添加到 Libevent 队列中。有 4 条路径可以让事件跳转到此状态:
-
调用
event_new
函数,返回一个事件对象,或调用event_assign
赋值一个事件对象。这两个函数本质是一样的,一个是返回事件对象,一个是作为参数传递事件对象,一般第一种用的多。这两个函数在 Libevent 中的定义如下struct event *event_new( struct event_base *, evutil_socket_t, short, event_callback_fn, void *); int event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *); // 回调函数指针定义 typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
-
pinding 状态的事件调用
event_del
函数后,也会返回到此状态; -
active 状态的事件调用完成(即执行完事件回调函数),若没有定义持久化,也会返回到此状态。
-
-
待决(pending):此状态表示事件已经位于 Libevent 队列中,可以随时准备执行。有 4 条路径可以让事件跳转到此状态:
- 调用
event_add
之后,事件进入到此状态; - active 状态的事件调用完成(即执行完事件回调函数),若定义为持久化,也会返回到此状态。
- 调用
-
激活(active):此状态表示事件正在执行中,也包括超时事件。这是由 Libevent 进行调度的,用户也可以手动调用
event_active
函数让事件转为此状态。 -
持久的(persistent):严格来讲,这应该是一个事件表示,这个标识非常重要,会让事件跳转到不同的状态,所以单独进行说明。从图中可以看出:
- 若设置为持久化,事件完成后状态会由 active 变为 pending ,可仍由 Libevent 调用,不用做其他操作;
- 若未设置,则表示事件完成后状态会由 active 变为 non-pending ,即初始化状态,此时需要再次调用
event_add
函数,将事件变为 pending 状态,事件才会再次被执行。
Libevent 事件的状态与操作系统进程的状态类似,可以用于类比,如下图所示
Libevent 事件处理
本章详细描述 Libevent 事件处理的 API。
event_new 函数
功能:创建一个事件,将事件设置为 non-pending 状态。函数定义如下:
struct event *event_new(
struct event_base *base, // Libevent上下文
evutil_socket_t fd, // 监听的文件描述符
short what, // 事件标志
event_callback_fn cb, void *arg); // 事件回调函数和参数
-
struct event_base *base
Libevent 上下文; -
evutil_socket_t fd
监听的文件描述符; -
short what
事件标志,定义与头文件 event2/event.h ,主要事件标志有:- EV_TIMEOUT 超时,默认是忽略的,一般不用,直接设置超时时间即可;
- EV_READ 读事件;
- EV_WRITE 写事件;
- EV_SIGNAL 信号事件;
- EV_PERSIST 持久化事件;
- EV_ET 边沿触发,只有在事件变化时才触发,需要底层支持,一般在 epoll 中使用。Libevent 2.0 以上支持,与 EV_READ 和 EV_WRITE 配合使用,如:
EV_READ | EV_PERSIST, EV_READ | EV_ET
。
-
event_callback_fn cb
事件回调函数typedef void (*event_callback_fn)(evutil_socket_t fd, short what, void *arg);
-
evutil_socket_t fd
发生事件的文件描述符,这里不限于 socket,也可以是文件描述符,定义如下:#ifdef _WIN32 #define evutil_socket_t intptr_t #else #define evutil_socket_t int #endif
-
short what
事件标志,定义如上; -
void *arg
传递的参数,即 event_new 中的最后一个参数。
-
-
void *arg
传递给回调函数的参数。
event_add 函数
功能:将事件添加到 Libevent 中,事件会变为 pending 状态。若是定时器事件,会添加到优先级队列(小根堆实现)中。函数定义如下
int event_add(struct event *ev, const struct timeval *timeout);
-
struct event *ev
event_new 创建的事件; -
struct timeval *timeout
超时时间,不超时传递 NULL。timeval 中有两个字段,分别表示秒和微秒。定义如下:struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ };
event_add 函数源码
int event_add(struct event *ev, const struct timeval *tv)
{
int res;
if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
event_warnx("%s: event has no event_base set.", __func__);
return -1;
}
EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
res = event_add_nolock_(ev, tv, 0);
EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
return (res);
}
通过代码可知,主要实现位于函数 event_add_nolock_
中,此函数比较长,核心就是根据事件标志对不同事件(IO 事件、信号事件和超时事件)进行处理。默认情况有加锁操作 EVBASE_ACQUIRE_LOCK / EVBASE_RELEASE_LOCK
,若是单线程调用,可以配置为不加锁,即在 config 中设置 EVENT_BASE_FLAG_NOLOCK ,这样效率会更高一些。
event_del 函数
功能:将事件从 Libevent 中删除,事件会变为 non-pending 状态。函数定义如下
int event_del(struct event *ev);
struct event *ev
event_new 创建的事件。
注意:如果当前事件不是处于 pending 或 active 状态,则没有影响。
event_free 函数
功能:释放事件资源,事件变为未初始化状态。函数定义如下
void event_free(struct event *ev);
struct event *ev
event_new 创建的事件。
注意:调用此函数后,事件不再可用,此时 ev 变为野指针,类似与调用 C 的 free 函数。
下面是 Libevent 实现的一个回显服务器的示例,即收到客户端的数据后返回 OK。都是采用最基础的函数实现的,在 Windows/Linux 上都可以运行:
// desc: libevent demo simple server
#include <iostream>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <signal.h>
#endif // !_WIN32
#include "event.h"
#define SERVER_PORT 8000
// 客户端数据读取事件
void client_cb(evutil_socket_t s, short what, void *arg)
{
// 水平触发LT:只要有数据没有处理,就会一直触发
// 边沿触发ET:只处理一次,不管数据是否处理完
event *ev = (event*)arg;
// 判断超时
if (what & EV_TIMEOUT) {
// 需要清理event
std::cout << "timeout" << std::endl;
event_free(ev);
evutil_closesocket(s);
return;
}
// 这是是为了测试,每次只读取4个数据,实际使用中不会这样用的
char buf[5] = { 0 };
int len = recv(s, buf, sizeof(buf) - 1, 0);
if (len > 0) {
std::cout << buf << std::endl;
send(s, "OK", 2, 0);
}
else {
// 需要清理event
std::cout << "event_free" << std::endl;
event_free(ev);
evutil_closesocket(s);
}
}
// 接收连接的回调函数
void listen_cb(evutil_socket_t s, short what, void *arg)
{
std::cout << "listen_cb" << std::endl;
// 读取连接信息
sockaddr_in sin;
socklen_t len = sizeof(sin);
evutil_socket_t client = accept(s, (sockaddr*)&sin, &len);
char ip[16] = { 0 };
evutil_inet_ntop(AF_INET, &sin.sin_addr, ip, sizeof(ip) - 1);
std::cout << "client ip: " << ip << ":" << ntohs(sin.sin_port) << std::endl;
// 客户端数据读取事件
event_base *base = (event_base*)arg;
// 水平触发
//event* ev = event_new(base, client, EV_READ | EV_PERSIST, client_cb, event_self_cbarg());
// 边沿触发,Windows上无效
event* ev = event_new(base, client, EV_READ | EV_PERSIST | EV_ET, client_cb, event_self_cbarg());
timeval tv = { 10, 0 }; // 10秒超时
event_add(ev, &tv);
}
int main(int argc, char* argv[])
{
#ifdef _WIN32
// 初始化socket库
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#else
// 忽略管道信号,因为发送数据给已关闭的socket会生成SIGPIPE信号,导致进程退出
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return -1;
#endif
std::cout << "libevent echo server test" << std::endl;
// 创建libevent上下文
event_base *base = event_base_new();
if (!base) {
std::cout << "event_base_new failed" << std::endl;
return -1;
}
std::cout << "event_base_new success" << std::endl;
// 创建 socket
evutil_socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock <= 0) {
std::cout << "create socket error: " << strerror(errno) << std::endl;
return -1;
}
// 设置非阻塞和地址复用
evutil_make_socket_nonblocking(sock);
evutil_make_listen_socket_reuseable(sock);
// 绑定IP和端口
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERVER_PORT);
int ret = ::bind(sock, (sockaddr*)&sin, sizeof(sin));
if (ret != 0) {
std::cout << "bind socket error: " << strerror(errno) << std::endl;
return -1;
}
// 开始监听,建立10个连接的缓存
listen(sock, 10);
// 创建监听事件,并添加到libevent中
event* ev = event_new(base, sock, EV_READ | EV_PERSIST, listen_cb, base);
event_add(ev, 0);
// 事件分发处理
event_base_dispatch(base);
// 释放资源
if (ev) event_free(ev);
event_base_free(base);
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}
特别说明,上面的代码为了测试,加了很多功能,比如故意将接收缓冲区设置的很小,是为了测试边沿触发。还有,10s 内未收到数据,则服务端自动断开连接,实际使用过程中不用这样处理。
最后
以上就是美丽墨镜为你收集整理的Libevent 学习四:事件状态和事件处理Libevent 事件状态Libevent 事件处理的全部内容,希望文章能够帮你解决Libevent 学习四:事件状态和事件处理Libevent 事件状态Libevent 事件处理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复