服务端事件组成
1 网络io事件
- linux:epoll、poll、select
- mac:kqueue
- window:iocp
libevent/libev 对 epoll/poll/select/kqueue/icop等进行了封装,根据不同平台下选择不同的多路复用IO接口。
2 定时事件
- 红黑树
- 最小堆:二叉树、四叉树
- 跳表
- 时间轮
libevent/libev 对定时事件数据结构等进行了封装。libevent的定时事件数据结构是二叉树最小堆;libev的定时事件数据结构是四叉树最小堆。
3 信号事件
libevent 与 libev 概述
概念
libevent和libev都是c语言实现的异步事件库;通过注册异步事件,库检测事件触发,从而库根据发生事件的先后顺序,调用相应回调函数进行处理;
事件包括:网络io事件,定时事件,信号事件;
事件循环:等待并分发事件;用于管理事件;
libevent 和 libev 主要封装了异步事件库与操作系统的交互(主要对epoll的封装、定时事件、信号事件的统一管理,所有对外的事件接口是异步接口);让用户不用关注平台的差异,只需着手事件的具体处理;
libevent 和 libev 对window支持都比较差,因此产生 libuv 库,libuv 基于 libev,但是 window上封装了 iocp;node.js基于libuv;
区别
从设计理念出发,libev 是为了改进 libevent 中的一些架构决策,例如,libevent 中全局变量的使用使得在多线程环境中很难安全地使用,watcher 的数据结构设计太大,因为它们将 I/O、时间和信号处理放在一个结构体中,额外的组件如 http 、dns、openssl, 服务器由于实现质量差以及由此产生的安全问题,计时器不精确,不能很好地处理时间事件。
libev 通过不使用全局变量,而是对所有回调函数传参的方式传递上下文;并且根据不同事件类型构建不同的数据结构,这样以来减低事件的耦合性;
libevent的定时事件管理的数据结构是二叉树最小堆;libev的定时事件管理的数据结构是四叉树最小堆,经过验证在>5000数量级,四叉树最小堆比二叉树最小堆效率高5%。
libev 小而高效;只关注事件处理;而libevent集成一些openssl、http协议解析等额外的组件。
libevent
编译
aclocal
libtoolize --force
autoheader
automake --add-missing
autoconf
./configure && make && make install
特色
bufferevent
提供 bufferevent,进一步提供管理读写事件(包括连接断开事件),以及读写数据缓冲(bufferevent里封装了每个tcp连接的用户层读/写缓冲区);
- bufferevent_socket_new
- bufferevent_socket_connect
- bufferevent_free
- bufferevent_setcb
- bufferevent_enable
- bufferevent_disable
- bufferevent_get_input //读缓冲区
- bufferevent_get_output //写缓冲区
- bufferevent_write
- bufferevent_write_buffer
- bufferevent_read
- bufferevent_read_buffer
evconnlistener
提供了监听和接受 tcp 连接的方法
- evconnlistener_new
- evconnlistener_new_bind
- evconnlistener_free
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd);
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen);
void evconnlistener_free(struct evconnlistener *lev);
/** Flag: Indicates that we should not make incoming sockets nonblocking
* before passing them to the callback. */
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING (1u<<0)
/** Flag: Indicates that freeing the listener should close the underlying
* socket. */
#define LEV_OPT_CLOSE_ON_FREE (1u<<1)
/** Flag: Indicates that we should set the close-on-exec flag, if possible */
#define LEV_OPT_CLOSE_ON_EXEC (1u<<2)
/** Flag: Indicates that we should disable the timeout (if any) between when
* this socket is closed and when we can listen again on the same port. */
#define LEV_OPT_REUSEABLE (1u<<3)
/** Flag: Indicates that the listener should be locked so it's safe to use
* from multiple threadcs at once. */
#define LEV_OPT_THREADSAFE (1u<<4)
/** Flag: Indicates that the listener should be created in disabled
* state. Use evconnlistener_enable() to enable it later. */
#define LEV_OPT_DISABLED (1u<<5)
/** Flag: Indicates that the listener should defer accept() until data is
* available, if possible. Ignored on platforms that do not support this.
*
* This option can help performance for protocols where the client transmits
* immediately after connecting. Do not use this option if your protocol
* _doesn't_ start out with the client transmitting data, since in that case
* this option will sometimes cause the kernel to never tell you about the
* connection.
*
* This option is only supported by evconnlistener_new_bind(): it can't
* work with evconnlistener_new_fd(), since the listener needs to be told
* to use the option before it is actually bound.
*/
#define LEV_OPT_DEFERRED_ACCEPT (1u<<6)
/** Flag: Indicates that we ask to allow multiple servers (processes or
* threads) to bind to the same port if they each set the option.
*
* SO_REUSEPORT is what most people would expect SO_REUSEADDR to be, however
* SO_REUSEPORT does not imply SO_REUSEADDR.
*
* This is only available on Linux and kernel 3.9+
*/
#define LEV_OPT_REUSEABLE_PORT (1u<<7)
主要接口
event_base_new
初始化 libevent;对应理解 epoll_create
struct event_base *event_base_new(void);
event_new
创建事件,初始化event和相应的回调函数
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events,
void (*cb)(evutil_socket_t, short, void *), void *arg)
event_set
设置事件
void
event_set(struct event *ev, evutil_socket_t fd, short events,
void (*callback)(evutil_socket_t, short, void *), void *arg)
event_base_set
建立 event 与 event_base 的映射关系;
int event_base_set(struct event_base *eb, struct event *ev);
event_add
注册事件,包括时间事件;相当于 epoll_ctl;
int
event_add(struct event *ev, const struct timeval *tv)
event_del
注销事件
int
event_del(struct event *ev)
event_base_loop
进入事件循环
int
event_base_loop(struct event_base *base, int flags)
注意
event_new 相当于 malloc + event_set + event_base_set
示例代码
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include "event2/event.h"
void socket_read_cb(int fd, short events, void *arg);
void socket_accept_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_cb, (void*)ev);
event_add(ev, NULL);
}
void socket_read_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", msg);
char reply_msg[4096] = "recvieced msg: ";
strcat(reply_msg + strlen(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);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
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);
return -1;
}
evutil_make_socket_nonblocking(listenfd);
return listenfd;
}
int main(int argc, char** argv)
{
int listenfd = socket_listen(8989);
if (listenfd == -1)
{
printf("socket_listen error\n");
return -1;
}
struct event_base* base = event_base_new();
struct event* ev_listen = event_new(base, listenfd, EV_READ | EV_PERSIST,
socket_accept_cb, base);
event_add(ev_listen, NULL);
event_base_dispatch(base);
return 0;
}
/*
gcc main.c -o main -I../libevent/include -L../libevent/.libs -levent
client:
telnet 127.0.0.1 8989
*/
libev
libev的主要数据结构
EV_WATCHER
/* shared by all watchers */
#define EV_WATCHER(type) \\
int active; /* 表示 watcher 是否活跃,active = 1 表示还没被 stop 掉 */ \\
int pending; /* 存储 watcher 在 pendings 中的索引。大于零表示还没被处理。
* watcher 的回调函数被调用后,会设置为 0。 */ \\
int priority; /* 事件的优先级 */ \\
void *data; /* 回调函数所需要的数据 */ \\
void (*cb)(EV_P_ struct type *w, int revents); /* 回调函数 */
作用:不同事件类型的共有信息。
EV_WATCHER_LIST
#define EV_WATCHER_LIST(type) \\
EV_WATCHER (type) \\
struct ev_watcher_list *next; /* 同一个文件描述符上可以被注册多个 watcher,比如:监听
是否可读/可写 */
作用:watcher 链表
ev_io
typedef struct ev_io
{
EV_WATCHER_LIST (ev_io)
int fd;
int events;
} ev_io;
// 作用:记录 IO 事件的基本信息。
// ev_io 相比 ev_watcher 增加了 next, fd, events 的属性。
ANFD
/* file descriptor info structure */
typedef struct
{
WL head; /* 同一个 fd 上的所有 ev_watcher 事件 */
unsigned char events; /* the events watched for,通常被设置成所有 ev_watcher->events 的或集。 */
unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET)
* 默认值为 0,当调用 ev_io_start 后,reify 会被设置为 `w->events & EV__IOFDSET | EV_ANFD_REIFY`。
* 如果 reify 未被设置,则把 fd 添加到 fdchanges 中去。*/
...
} ANFD;
// 作用:
// 解决根据 fd 快速找到与其相关的事件。
// libev 的方法是用 anfds 数组来存所有 fd 信息的结构体,然后以 fd 值为索引直接找到对应的结构体。
ANPENDING
/* stores the pending event set for a given watcher */
typedef struct
{
W w;
int events; /* the pending event set for the given watcher */
} ANPENDING;
// 作用:存储已准备好的 watcher,等待回调函数被调用。
ev_loop
struct ev_loop {
double ev_rt_now; /* 当前的时间戳 */
int backend; /* 采用哪种多路复用方式, e.g. SELECT/POLL/EPOLL */
int activecnt; /* total number of active events ("refcount") */
int loop_done; /* 事件循环结束的标志,signal by ev_break */
int backend_fd; /* e.g. epoll fd, created by epoll_create*/
void (*backend_modify)(EV_P_ int fd, int oev, int nev)); /* 对应 epoll_ctl */
void (*backend_poll)(EV_P_ ev_tstamp timeout)); /* 对应 epoll_wait */
void (*invoke_cb)(struct ev_loop *loop);
ANFD *anfds; /* 把初始化后的 ev_io 结构体绑定在 anfds[fd].head 事件链表上,方便根据 fd 直接查找。*/
int *fdchanges; /* 存放需要 epoll 监听的 fd */
ANPENDING *pendings [NUMPRI]; /* 存放等待被调用 callback 的 watcher */
}
// 作用:基本包含了 loop 循环所需的所有信息,为让注释更容易理解采用 epoll 进行说明。
libev的主要接口
ev_io_init
初始化 watcher 的 fd/events/callback
#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev), (fd),(events)); } while (0)
ev_io_start
注册并绑定 io watcher 到 ev_loop
void ev_io_start(struct ev_loop *loop, ev_io *w)
ev_timer_start
注册并绑定 timer watcher 到 ev_loop
void ev_timer_start(struct ev_loop *loop, ev_timer *w)
ev_run
开启改 ev_loop 的事件循环
int ev_run(struct ev_loop *loop, int flags)
示例代码
使用libev风格代码:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <time.h>
#include "ev.h"
static void
do_accept(struct ev_loop* reactor, ev_io* w, int events) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
socklen_t len = sizeof(addr);
int clientfd = accept(w->fd, (struct sockaddr *)&addr, &len);
if (clientfd != -1)
close(clientfd);
printf("accept fd = %d\n", clientfd);
}
int socket_listen(uint16_t port) {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listenfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1)
return -1;
if (listen(listenfd, 5) == -1)
return -1;
printf("server start listening port:%d\n", port);
return listenfd;
}
static void
do_timer(struct ev_loop *loop, ev_timer *timer_w, int e) {
time_t now = time(NULL);
printf("do_timer %s", (char*)ctime(&now));
// ev_timer_stop(loop, timer_w);
}
int main() {
struct ev_loop *loop = ev_loop_new(0);
struct ev_timer t1;
ev_timer_init(&t1, do_timer, 1, 1);
ev_timer_start(loop, &t1);
int listenfd = socket_listen(8989);
if (listenfd == -1) return -2;
struct ev_io i1;
ev_io_init(&i1, do_accept, listenfd, EV_READ);
ev_io_start(loop, &i1);
ev_run(loop, 0);
ev_loop_destroy(loop);
return 0;
}
/*
gcc main-ev.c -o main-ev -I../libev/ -L../libev/.libs/ -lev
*/
使用libevent风格代码:
注意,本质上还是使用libev库,只是libev为了兼容libevent的代码,进行了一层封装,具体封装细节在头文件在"/libev/event.h"中。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>
#include "event.h"
static struct event evtimer;
static void
do_accept(int fd, short events, void* arg) {
struct event_base *ev = (struct event_base *)arg;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
socklen_t len = sizeof(addr);
int clientfd = accept(fd, (struct sockaddr *)&addr, &len);
if (clientfd != -1)
close(clientfd);
printf("accept fd = %d\n", clientfd);
}
static void
do_timer(int fd, short events, void* arg) {
time_t now = time(NULL);
printf("do_timer %s", (char*)ctime(&now));
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
//因为libevent版本的回调函数参数无法获得event所属的哪个,所以需要做成evtimer全局变量
event_add(&evtimer, &tv);
}
int socket_listen(uint16_t port) {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listenfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1)
return -1;
if (listen(listenfd, 5) == -1)
return -1;
printf("server start listening port:%d\n", port);
return listenfd;
}
int main() {
struct event_base* ev = event_init();
struct event evlisten;
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
int listenfd = socket_listen(8989);
if (listenfd == -1) return -2;
event_set(&evlisten, listenfd, EV_READ | EV_PERSIST, do_accept, ev);
event_base_set(ev, &evlisten);
event_add(&evlisten, NULL);
event_set(&evtimer, -1, 0, do_timer, ev);
event_base_set(ev, &evtimer);
event_add(&evtimer, &tv);
event_base_dispatch(ev);
event_base_free(ev);
return 0;
}
/*
// gcc main-event.c -o main-event -I../libev/ -L../libev/.libs/ -lev
*/