3. 使用实例
知道一个框架如何使用能够更好的帮助我们从整体上把我该框架的逻辑架构,使我们在阅读源码时更具有方向和针对性,起到穿针引线的作用。下面先来看一个使用libevent的小例子。
#include <sys/signal.h>
#include "event2/event.h"
//信号处理函数
void signal_cb(int fd, short event, void *arg)
{
struct event_base * base = (struct event_base *)arg;
struct timeval delay = {2, 0};
printf("Caught an interrupt signal; exiting in two seconds.\n");
event_base_loopexit(base, &delay);
}
//超时回调函数
void timeout_cb(int fd, short event, void *argc)
{
printf("timeout\n");
}
int main(int argc, char **argv)
{
//注意,旧版本的event_init返回值是void类型 除了调用event_init也可以调用event_base_new
struct event_base* base = event_init();
//创建一个信号事件
struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, base);
//向事件循环注册
event_add(signal_event, NULL);
struct timeval tv = {1, 0};
//创建一个定时器事件
struct event* timeout_event = evtimer_new(base, timeout_cb, NULL);
//向事件循环注册
event_add(timeout_event, &tv);
//事件分发
event_base_dispatch(base);
//释放相关资源
event_free(timeout_event);
event_free(signal_event);
event_base_free(base);
}
上面的小程序主要包括一个定时器事件和一个信号事件的注册和激活后的回调处理,从这个例子中,我们可以看出libevent使用的一般步骤。
1) 调用event_base_new或event_init新建一个Reactor框架对象event_base实例,原型如下:
struct event_base *event_init(void);//注:有些早期版本返回值为void *
struct event_base *event_base_new(void);
具体实现等了解了event_base,event等libevent的几个主要对象后再慢慢看。
2) 调用event_new创建具体的事件处理器event并设置从属的event_base实例,同时设置关心的事件和事件触发后的回调函数,若为信号事件,可调用evsignal_new创建;若为定时器事件,可调用evtimer_new创建。evsignal_new和evtimer_new实际上是两个宏定义,实际依旧是调用了event_new函数,相关代码如下:
#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
struct event * event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg);
event_new包括四个参数,第一个参数base表示新建事件从属的event_base实例;第二个参数表示fd表示关心的句柄,可以是文件句柄也可以是信号值;第三个参数表示关心的事件,包括EV_READ(IO读)、EV_WRITE(IO写)、EV_SIGNAL(信号)等事件,详细的我们后续再介绍。第四个参数表示事件触发后,执行哪个回调函数;第五个参数表示执行回调函数cb时的参数。
3) 调用event_add将事件处理器向Reactor注册,添加到相关事件队列中
int event_add(struct event *ev, const struct timeval *tv);
event_add包含两个参数,ev是需要注册的事件,若tv不为空,则表示该事件是个定时器事件,tv为超时时间
4) 调用event_base_dispatch函数执行事件循环,监测相关事件
int event_base_dispatch(struct event_base *);
参数是当前事件循环所属的event_base, 执行事件循环实际上是执行了一个while循环,循环里面对定时器超时时间进行检测,并调用select、epoll等事件多路分发函数对注册的句柄进行监控,直到没有需要再监控的句柄或调用了event_base_loopexit或event_base_loopbreak()退出循环函数,函数具体内容详见后文
5) 可调用event_base_loopexit退出事件循环
int event_base_loopexit(struct event_base *, const struct timeval *);
第一个参数表示要退出的事件循环所属的event_base实例,第二个参数表示延时时间,即经过多少时间后退出循环
附上一个用libevent实现的简单服务器的小例子,演示如何用libevent编写服务器程序
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include "event2/event.h"
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define MAXBUFLEN 1024
void do_accept(evutil_socket_t fd, short event, void *arg);
void do_read(evutil_socket_t fd, short event, void *arg);
void do_write(evutil_socket_t fd, short event, void *arg);
typedef struct _client
{
char buf[MAXBUFLEN];
//已经写到的位置
int n_written;
//需要写到的位置
int n_write_to;
//存储偏移量
int pos;
//读事件
struct event * read_event;
//写事件
struct event * write_event;
}client;
client * alloc_client(struct event_base * base,int fd)
{
client *cli = (client *)malloc(sizeof(client));
cli->pos = cli->n_written = cli->n_write_to = 0;
cli->read_event = event_new(base, fd, EV_READ | EV_PERSIST, do_read, cli);
cli->write_event = event_new(base, fd, EV_WRITE | EV_PERSIST, do_write, cli);
return cli;
}
void free_client(client * cli)
{
//event_free内部将会调用event_del从Reactor中取消注册相关事件
event_free(cli->read_event);
event_free(cli->write_event);
free(cli);
}
void do_write(evutil_socket_t fd, short events, void *arg)
{
printf("write back to client.\n");
ssize_t len;
client *cli = (client *)arg;
while(cli->n_written < cli->n_write_to)
{
len = send(fd, cli->buf + cli->n_written, cli->n_write_to - cli->n_written, 0);
if(len < 0)
{
if(errno == EAGAIN)
return;
close(fd);
free_client(cli);
}
cli->n_written += len;
}
if(cli->n_written == cli->pos)
cli->n_write_to = cli->pos = cli->n_written = 0;
event_del(cli->write_event);
}
void do_read(evutil_socket_t fd, short events, void *arg)
{
char buf[MAXBUFLEN] = {0};
ssize_t result = 0;
int i = 0;
client * cli = (client *)arg;
while(1)
{
result = recv(fd, buf, MAXBUFLEN, 0);
if(result <= 0)
break;
printf("get something\n");
for(i = 0; i < result; i++)
{
cli->buf[cli->pos++] = buf[i];
if(buf[i] == '\n')
{
cli->n_write_to = cli->pos;
event_add(cli->write_event, NULL);
}
}
}
if(result == 0) //对端已关闭连接
{
printf("连接已关闭\n");
close(fd);
free_client(cli);
}
else if(result <0)
{
if(errno == EAGAIN)
return;
perror("recv");
close(fd);
free_client(cli);
}
}
void do_accept(evutil_socket_t fd, short events, void *arg)
{
printf("get a new connection.\n");
struct event_base* base = (struct event_base *)arg;
struct sockaddr_storage client_addr;
socklen_t len = 0;
int cli_fd = 0;
if((cli_fd = accept(fd, (struct sockaddr *)&client_addr, &len)) < 0)
{
perror("accept");
return;
}
evutil_make_socket_nonblocking(cli_fd);
//建立新连接成功,将该句柄加入事件循环
client * cli = alloc_client(base, cli_fd);
event_add(cli->read_event, NULL);
}
int main(int argc, char **argv)
{
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = INADDR_ANY;
serv.sin_port = htons(9211);
int fd = socket(PF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
return -1;
}
int res;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res));
//将监听socket设置为非阻塞
evutil_make_socket_nonblocking(fd);
if(bind(fd, (struct sockaddr *)&serv, sizeof(serv)) < 0)
{
perror("bind");
return -1;
}
if(listen(fd, 5) < 0)
{
perror("listen");
return -1;
}
//创建一个Reactor
struct event_base * base = event_base_new();
//为监听句柄创建一个持久化的可读事件, 回调函数为do_accept,当fd可读时,将会调用do_accept
struct event * listen_event = event_new(base, fd, EV_READ | EV_PERSIST, do_accept, (void *)base);
//将事件处理器注册到事件循环
event_add(listen_event, NULL);
//开始事件循环
event_base_dispatch(base);
//释放Reactor
event_base_free(base);
}