Libevent
1.介绍
Libevent 是一个轻量级的开源高性能网络库,有几个显著的亮点:
- 事件驱动(event-driven),高性能;
- 轻量级,专注于网络,不如 ACE 那么臃肿庞大;
- 线程安全。Libevent 使用 libevent_pthreads 库来提供线程安全支持;
- 跨平台,支持 Windows、Linux、*BSD 和 Mac Os;
- 支持多种 I/O 多路复用技术, epoll、poll、dev/poll、select 和 kqueue 等;
- 支持 I/O,定时器和信号等事件;
- 注册事件优先级; Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、Vomit、Nylon、Netchat 等等。
2.Reactor概述
Reactor 模式直译过来就是反应堆模式,也被称为 Dispatcher 模式。Reactor 是一种事件驱动机制,应用程序需要提供事的接口注册到 Reactor 上,如果有相应的事件发生,Reactor 就会主动分发给对应的接口进行处理。这种机制和好莱坞法则非常契合:不要打电话给我们,我们会打电话给你。
简单来说,Reactor 模式的实现包含三步:
1.注册感兴趣的事件;
2.扫描是否有感兴趣的事件发生;
3.事件发生后做出相应的处理;
3.Libevent的一般使用模型
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <event.h>
void time_cb(int fd, short event, void *argc)
{
printf("timer wakeup\n");
}
int main()
{
struct event_base *base = event_init();
struct timeval tv;
tv.tv_sec = 10; // 10s
tv.tv_usec = 0;
struct event* time_ev = evtimer_new(base, time_cb, NULL);
event_add(time_ev, &tv);
event_base_dispatch(base);
}
处理流程
1) 首先应用程序准备并初始化 event,设置好事件类型和回调函数;
2) 向 libevent 添加该事件 event。对于定时事件,libevent 使用一个小根堆管理,key 为超时时间;对于 Signal 和 I/O 事件,libevent 将其放入到等待链表(wait list)中,这是一 个双向链表结构;
3) 程序调用 event_base_dispatch()系列函数进入无限循环,等待事件,以 select()函数为例; 每次循环前 libevent 会检查定时事件的最小超时时间 tv,根据 tv 设置 select()的最大等 待时间,以便于后面及时处 理超时事件;
当 select()返回后,首先检查超时事件,然后检查 I/O 事件;
Libevent 将所有的就绪事件,放入到激活链表中;
然后对激活链表中的事件,调用事件的回调函数执行事件处理;
流程图:
3.Libevent 支持的事件类型
4.libevent的主要接口
1.event_base_new
初始化 libevent;对应理解 epoll_create
struct event_base *event_base_new(void);
2.event_new
struct event *event_new(struct event_base *base, evutil_socket_t fd,
short events, void (*cb)(evutil_socket_t, short, void*), void *arg);
//创建通用事件处理器,初始化event和相应的回调函数
//分配并赋值新的event结构,准备用于添加和删除,即event_add() 或 event_del()
参数1:base表示event base;指定新创建的事件处理器从属的Reactor
参数2:fd,指定与该事件处理器关联的句柄。创建IO事件处理器时,应该给参数传递文件描述符值;创建信号 事件处理器时,应该给参数传递信号值,创建定时事件处理器时,则应该给f参数传递 -1。
参数3:events,需要监控的事件:EV_READ, EV_WRITE, EV_SIGNAL, EV_PERSIST, EV_ET的位与。
参数4:callback,回调函数
参数5:callback_arg, 回调函数的参数
如果事件包含EV_READ,EV_WRITE,或者他们的组合,那么fd这个文件描述符或者socket将要被监视什么时候可读,什么时候可写。如果事件包含EV_SIGNAL,那么fd就是需要等待事件的号码。如果事件不包含上面的任何标志,那么该事件只能被一个超时触发或者被event_active()手动的激活:这种情况,fd必须是-1。
3.evsignal_new
#define evsignal_new(b, x, cb, arg)
创建信号事件处理器,相当于 event_new((b), (x), EV_SIGNAL | EV_PERSIST, (cb), (arg))
4.evtimer_new
#define evtimer_new(b, cb, arg)
创建定时间事件处理器,相当于event_new((b), -1, 0, (cb), (arg))
5.event_set
设置事件
void
event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
6.event_base_set
建立 event 与 event_base 的映射关系;
int
event_base_set(struct event_base *eb, struct event *ev);
7.event_add
注册事件,包括时间事件;相当于 epoll_ctl;
int
event_add(struct event *ev, const struct timeval *tv);
8.event_del
注销事件
int
event_del(struct event *ev) ;
9.event_base_loop
进入事件循环
int
event_base_loop(struct event_base *base, int flags);
event_new 相当于 malloc + event_set + event_base_set
10.event_free
释放事件(包括资源释放)
void
event_free(struct event *ev);
可以简单的理解为对一棵树的操作
Libevent函数接口图:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
Libevent的两种使用层次
1.自己处理I/O
本地服务端代码:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include <event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void socket_read_client_cb(int fd, short events, void *arg);
void socket_accept_connect_cb(int fd, short events, void* arg)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
evutil_socket_t clientfd = accept(fd, (struct sockaddr*)&addr, &len);
evutil_make_socket_nonblocking(clientfd);
printf("accept a client %d\n", clientfd);
struct event_base* base = (struct event_base*)arg;
struct event *ev = event_new(NULL, -1, 0, NULL, NULL);
event_assign(ev, base, clientfd, EV_READ | EV_PERSIST,
socket_read_client_cb, (void*)ev);
event_add(ev, NULL);
}
void socket_read_client_cb(int fd, short events, void *arg)
{
char msg[4096];
struct event *ev = (struct event*)arg;
int len = read(fd, msg, sizeof(msg) - 1);
if( len <= 0 )
{
printf("client fd:%d disconnect\n", fd);
event_free(ev);
close(fd);
return;
}
msg[len] = '\0';
printf("recv the client msg: %s\n", msg);
char reply_msg[4096] = "sucessfully recvieced msg: ";
strcat(reply_msg , msg);
write(fd, reply_msg, strlen(reply_msg));
}
int socket_listen(int port)
{
int errno_save;
evutil_socket_t listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
return -1;
evutil_make_listen_socket_reuseable(listenfd);//设置 socket 为地址可重用,即将 socket 属性设为 SO_REUSEADD
//设置该标志后,Libevent会把该socket设置成reuseable。这样,关闭该socket后,其他socket就能马上使用同一个端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
sin.sin_port = htons(port);
if (bind(listenfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
evutil_closesocket(listenfd);
return -1;
}
if (listen(listenfd, 5) < 0) {
evutil_closesocket(listenfd);//关闭 socke
return -1;
}
evutil_make_socket_nonblocking(listenfd);//设置 socket 为非阻塞
return listenfd;
}
int main(int argc, char** argv)
{
int listenfd = socket_listen(6000);
if (listenfd == -1)
{
printf("socket_listen error\n");
return -1;
}
struct event_base* base = event_base_new();
struct event* ev_listenfd = event_new(base, listenfd, EV_READ | EV_PERSIST,
socket_accept_connect_cb, base);
event_add(ev_listenfd, NULL);
event_base_dispatch(base);
return 0;
}
2.I/O处理交给Libevent
待补充