在编程过程中,需要经常处理各种各样的事件(比如网络连接的建立和关闭、文件的读写等等)。这时候,我们需要设计一套高效的事件驱动机制。而在其中,libev便是一种非常优秀的选择,本文将从多个方面介绍如何使用libev进行事件驱动编程。
一、libev的基本概念
首先要介绍的是libev的基本概念。这些概念将会贯穿整篇文章。
1. event loop(事件循环)
事件循环是libev的核心,它可以同时处理多个事件,比如读写操作、定时器等等。事件循环执行的过程就是一个无限循环,将不断地接受事件,调用相应的回调函数进行处理。事件循环使用epoll系统调用进行监听。
#include <ev.h>
int main()
{
struct ev_loop *loop = ev_default_loop(0);
ev_loop(loop, 0);
return 0;
}
2. watcher(观察者)
事件循环中的观察者是libev的另一个核心概念,它代表着不同类型的事件。观察者有几种类型:
- IO观察者:通过该观察者可以等待文件描述符上的输入和输出事件。
- 定时器观察者:通过该观察者可以创建和管理定时器。
- 信号观察者:通过该观察者可以监听系统信号。
- 子进程观察者:通过该观察者可以监听子进程的退出事件。
观察者需要指定事件循环以及回调函数,当事件触发时,回调函数将被调用。
void cb(struct ev_loop *loop, ev_io *w, int revents)
{
// 调用回调函数
}
int main()
{
struct ev_loop *loop = ev_default_loop(0);
ev_io watcher;
ev_io_init(&watcher, cb, STDIN_FILENO, EV_READ);
ev_io_start(loop, &watcher);
ev_loop(loop, 0);
return 0;
}
二、使用IO观察者实现网络编程
1. 监听socket的事件循环
使用IO观察者实现网络编程时,需要在事件循环中监听socket的读写事件。当有连接进来时,回调函数将会被调用。
void on_connect(struct ev_loop *loop, ev_io *watcher, int revents)
{
if (EV_ERROR & revents) {
perror("got invalid event");
return;
}
...
// 新建一个IO观察者,并注册事件
struct ev_io *client_watcher = (struct ev_io*) malloc(sizeof(struct ev_io));
ev_io_init(client_watcher, on_read, client_sock_fd, EV_READ);
ev_io_start(loop, client_watcher);
}
int main()
{
...
// 创建socket,并监听
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
...
ev_io_init(&server_watcher, on_connect, server_sock_fd, EV_READ);
ev_io_start(loop, &server_watcher);
...
}
2. 处理客户端读写事件
在IO观察者的回调函数中,需要处理客户端的读写事件。当收到数据时,回调函数将会被调用。
void on_read(struct ev_loop *loop, ev_io *watcher, int revents)
{
if (EV_ERROR & revents) {
perror("got invalid event");
return;
}
...
// 读数据
ssize_t read_size = read(watcher->fd, buffer, BUF_SIZE);
...
// 写数据
ssize_t sent_size = send(watcher->fd, data, size, 0);
...
}
三、使用定时器观察者进行任务调度
1. 创建定时器观察者
使用定时器观察者可以管理定时器,即在一定时间后触发某个事件。创建定时器观察者时需要指定回调函数。
void on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents)
{
...
}
int main()
{
...
// 新建定时器观察者
ev_timer timeout_watcher;
ev_timer_init(&timeout_watcher, on_timeout, 2.0, 0.0);
ev_timer_start(loop, &timeout_watcher);
...
}
2. 取消定时器
如果需要取消定时器,可以使用ev_timer_stop函数。
ev_timer_stop(loop, &timeout_watcher);
四、使用信号观察者监听系统信号
1. 创建信号观察者
使用信号观察者可以监听系统信号,当信号被触发时回调函数将被调用。
void on_signal(struct ev_loop *loop, ev_signal *watcher, int revents)
{
if (watcher->signum == SIGINT) {
// Ctrl+C
...
// 停止事件循环
ev_break(loop, EVBREAK_ALL);
} else if (watcher->signum == SIGTERM) {
// kill命令
...
}
}
int main()
{
...
// 新建信号观察者
ev_signal signal_watcher;
ev_signal_init(&signal_watcher, on_signal, SIGINT);
ev_signal_start(loop, &signal_watcher);
...
}
2. 停止事件循环
使用ev_break函数可以停止事件循环。
ev_break(loop, EVBREAK_ALL);
五、使用子进程观察者实现异步任务
1. 创建子进程观察者
使用子进程观察者可以监听子进程的退出事件,并在子进程退出后调用回调函数。
void on_child_exit(struct ev_loop *loop, ev_child *watcher, int revents)
{
...
}
int main()
{
...
// 创建一个子进程
pid_t pid = fork();
...
// 新建子进程观察者
ev_child child_watcher;
ev_child_init(&child_watcher, on_child_exit, pid, 0);
ev_child_start(loop, &child_watcher);
...
}
2. 启动异步任务
可以使用fork函数启动一个异步任务。
pid_t pid = fork();
if (pid == 0) {
// 子进程,执行异步任务
...
exit(0);
}
六、总结
到这里,我们已经介绍了如何使用libev进行事件驱动编程,包括使用IO观察者实现网络编程、使用定时器观察者进行任务调度、使用信号观察者监听系统信号以及使用子进程观察者实现异步任务。通过这些示例,读者可以更好地理解libev的基本概念以及如何使用它进行事件驱动编程。